1
0
Fork 0
mirror of synced 2024-06-01 15:01:10 -04:00
ultimate-vim/sources_non_forked/coc.nvim/src/commands.ts
2022-07-20 13:20:15 +08:00

427 lines
14 KiB
TypeScript

'use strict'
import { Neovim } from '@chemzqm/neovim'
import * as language from 'vscode-languageserver-protocol'
import { CodeAction, Disposable, InsertTextMode, Location, Position, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'
import { URI } from 'vscode-uri'
import diagnosticManager from './diagnostic/manager'
import Mru from './model/mru'
import Plugin from './plugin'
import snippetsManager from './snippets/manager'
import { UltiSnippetOption } from './types'
import { wait } from './util'
import window from './window'
import workspace from './workspace'
import events from './events'
const logger = require('./util/logger')('commands')
// command center
export interface Command {
readonly id: string | string[]
execute(...args: any[]): void | Promise<any>
}
class CommandItem implements Disposable, Command {
constructor(
public id: string,
private impl: (...args: any[]) => void,
private thisArg: any,
public internal = false
) {
}
public execute(...args: any[]): void | Promise<any> {
let { impl, thisArg } = this
return impl.apply(thisArg, args || [])
}
public dispose(): void {
this.thisArg = null
this.impl = null
}
}
export class CommandManager implements Disposable {
private readonly commands = new Map<string, CommandItem>()
public titles = new Map<string, string>()
public onCommandList: string[] = []
private mru: Mru
public init(nvim: Neovim, plugin: Plugin): void {
this.mru = workspace.createMru('commands')
this.register({
id: 'vscode.open',
execute: async (url: string | URI) => {
nvim.call('coc#ui#open_url', url.toString(), true)
}
}, true)
this.register({
id: 'workbench.action.reloadWindow',
execute: async () => {
nvim.command('CocRestart', true)
}
}, true)
this.register({
id: 'editor.action.insertSnippet',
execute: async (edit: TextEdit, ultisnip?: UltiSnippetOption) => {
const opts = ultisnip === true ? {} : ultisnip
return await snippetsManager.insertSnippet(edit.newText, true, edit.range, InsertTextMode.adjustIndentation, opts ? opts : undefined)
}
}, true)
this.register({
id: 'editor.action.doCodeAction',
execute: async (action: CodeAction) => {
await plugin.cocAction('doCodeAction', action)
}
}, true)
this.register({
id: 'editor.action.triggerSuggest',
execute: async () => {
let doc = workspace.getDocument(workspace.bufnr)
if (doc) await doc.synchronize()
nvim.call('coc#start', [], true)
}
}, true)
this.register({
id: 'editor.action.triggerParameterHints',
execute: async () => {
let doc = workspace.getDocument(workspace.bufnr)
if (doc) await doc.synchronize()
await plugin.cocAction('showSignatureHelp')
}
}, true)
this.register({
id: 'editor.action.addRanges',
execute: async (ranges: Range[]) => {
await plugin.cocAction('addRanges', ranges)
}
}, true)
this.register({
id: 'editor.action.restart',
execute: async () => {
await wait(30)
nvim.command('CocRestart', true)
}
}, true)
this.register({
id: 'editor.action.showReferences',
execute: async (_filepath: string, _position: Position, references: Location[]) => {
await workspace.showLocations(references)
}
}, true)
this.register({
id: 'editor.action.rename',
execute: async (uri: string, position: Position) => {
await workspace.jumpTo(uri, position)
await plugin.cocAction('rename')
}
}, true)
this.register({
id: 'editor.action.format',
execute: async () => {
await plugin.cocAction('format')
}
}, true)
this.register({
id: 'workspace.refactor',
execute: async (locations: Location[]) => {
let locs = locations.filter(o => Location.is(o))
await plugin.getHandler().refactor.fromLocations(locs)
}
}, true)
this.register({
id: 'workspace.clearWatchman',
execute: async () => {
let res = await window.runTerminalCommand('watchman watch-del-all')
if (res.success) window.showMessage('Cleared watchman watching directories.')
}
}, false, 'run watch-del-all for watchman to free up memory.')
this.register({
id: 'workspace.workspaceFolders',
execute: async () => {
let folders = workspace.workspaceFolders
let lines = folders.map(folder => URI.parse(folder.uri).fsPath)
await window.echoLines(lines)
}
}, false, 'show opened workspaceFolders.')
this.register({
id: 'workspace.renameCurrentFile',
execute: async () => {
await workspace.renameCurrent()
}
}, false, 'change current filename to a new name and reload it.')
this.register({
id: 'extensions.toggleAutoUpdate',
execute: async () => {
let config = workspace.getConfiguration('coc.preferences')
let interval = config.get<string>('extensionUpdateCheck', 'daily')
if (interval == 'never') {
config.update('extensionUpdateCheck', 'daily', true)
window.showMessage('Extension auto update enabled.', 'more')
} else {
config.update('extensionUpdateCheck', 'never', true)
window.showMessage('Extension auto update disabled.', 'more')
}
}
}, false, 'toggle auto update of extensions.')
this.register({
id: 'workspace.diagnosticRelated',
execute: () => diagnosticManager.jumpRelated()
}, false, 'jump to related locations of current diagnostic.')
this.register({
id: 'workspace.showOutput',
execute: async (name?: string) => {
if (name) {
window.showOutputChannel(name)
} else {
let names = workspace.channelNames
if (names.length == 0) return
if (names.length == 1) {
window.showOutputChannel(names[0])
} else {
let idx = await window.showQuickpick(names)
if (idx == -1) return
let name = names[idx]
window.showOutputChannel(name)
}
}
}
}, false, 'open output buffer to show output from languageservers or extensions.')
this.register({
id: 'document.showIncomingCalls',
execute: async () => {
await plugin.cocAction('showIncomingCalls')
}
}, false, 'show incoming calls in tree view.')
this.register({
id: 'document.showOutgoingCalls',
execute: async () => {
await plugin.cocAction('showOutgoingCalls')
}
}, false, 'show outgoing calls in tree view.')
this.register({
id: 'document.echoFiletype',
execute: async () => {
let bufnr = await nvim.call('bufnr', '%')
let doc = workspace.getDocument(bufnr)
if (!doc) return
await window.echoLines([doc.filetype])
}
}, false, 'echo the mapped filetype of the current buffer')
this.register({
id: 'document.renameCurrentWord',
execute: async () => {
let bufnr = await nvim.call('bufnr', '%')
let doc = workspace.getDocument(bufnr)
if (!doc) return
let edit = await plugin.cocAction('getWordEdit') as WorkspaceEdit
if (!edit) {
window.showMessage('Invalid position', 'warning')
return
}
let ranges: Range[] = []
let { changes, documentChanges } = edit
if (changes) {
let edits = changes[doc.uri]
if (edits) ranges = edits.map(e => e.range)
} else if (documentChanges) {
for (let c of documentChanges) {
if (TextDocumentEdit.is(c) && c.textDocument.uri == doc.uri) {
ranges = c.edits.map(e => e.range)
}
}
}
if (ranges.length) {
await plugin.cocAction('addRanges', ranges)
}
}
}, false, 'rename word under cursor in current buffer by use multiple cursors.')
this.register({
id: 'document.jumpToNextSymbol',
execute: async () => {
let doc = await workspace.document
if (!doc) return
let ranges = await plugin.cocAction('symbolRanges') as Range[]
if (!ranges) return
let { textDocument } = doc
let offset = await window.getOffset()
ranges.sort((a, b) => {
if (a.start.line != b.start.line) {
return a.start.line - b.start.line
}
return a.start.character - b.start.character
})
for (let i = 0; i <= ranges.length - 1; i++) {
if (textDocument.offsetAt(ranges[i].start) > offset) {
await window.moveTo(ranges[i].start)
return
}
}
await window.moveTo(ranges[0].start)
}
}, false, 'Jump to next symbol highlight position.')
this.register({
id: 'workspace.undo',
execute: async () => {
await workspace.files.undoWorkspaceEdit()
}
}, false, 'Undo previous workspace edit')
this.register({
id: 'workspace.redo',
execute: async () => {
await workspace.files.redoWorkspaceEdit()
}
}, false, 'Redo previous workspace edit')
this.register({
id: 'workspace.inspectEdit',
execute: async () => {
await workspace.files.inspectEdit()
}
}, false, 'Inspect previous workspace edit in new tab')
this.register({
id: 'workspace.openLocation',
execute: async (winid: number, loc: Location, openCommand?: string) => {
if (winid) await nvim.call('win_gotoid', [winid])
await workspace.jumpTo(loc.uri, loc.range.start, openCommand)
}
}, true)
this.register({
id: 'document.jumpToPrevSymbol',
execute: async () => {
let doc = await workspace.document
if (!doc) return
let ranges = await plugin.cocAction('symbolRanges') as Range[]
if (!ranges) return
let { textDocument } = doc
let offset = await window.getOffset()
ranges.sort((a, b) => {
if (a.start.line != b.start.line) {
return a.start.line - b.start.line
}
return a.start.character - b.start.character
})
for (let i = ranges.length - 1; i >= 0; i--) {
if (textDocument.offsetAt(ranges[i].end) < offset) {
await window.moveTo(ranges[i].start)
return
}
}
await window.moveTo(ranges[ranges.length - 1].start)
}
}, false, 'Jump to previous symbol highlight position.')
this.register({
id: 'document.checkBuffer',
execute: async () => {
await plugin.cocAction('bufferCheck')
}
}, false, 'Check providers for current buffer.')
}
public get commandList(): CommandItem[] {
let res: CommandItem[] = []
for (let item of this.commands.values()) {
if (!item.internal) res.push(item)
}
return res
}
public dispose(): void {
for (const registration of this.commands.values()) {
registration.dispose()
}
this.commands.clear()
}
public execute(command: language.Command): Promise<any> {
return this.executeCommand(command.command, ...(command.arguments ?? []))
}
public register<T extends Command>(command: T, internal = false, description?: string): T {
for (const id of Array.isArray(command.id) ? command.id : [command.id]) {
this.registerCommand(id, command.execute, command, internal)
if (description) this.titles.set(id, description)
}
return command
}
public has(id: string): boolean {
return this.commands.has(id)
}
public unregister(id: string): void {
let item = this.commands.get(id)
if (!item) return
item.dispose()
this.commands.delete(id)
}
/**
* Registers a command that can be invoked via a keyboard shortcut,
* a menu item, an action, or directly.
*
* Registering a command with an existing command identifier twice
* will cause an error.
*
* @param command A unique identifier for the command.
* @param impl A command handler function.
* @param thisArg The `this` context used when invoking the handler function.
* @return Disposable which unregisters this command on disposal.
*/
public registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any, internal = false): Disposable {
if (id.startsWith("_")) internal = true
this.commands.set(id, new CommandItem(id, impl, thisArg, internal))
return Disposable.create(() => {
this.commands.delete(id)
})
}
/**
* Executes the command denoted by the given command identifier.
*
* * *Note 1:* When executing an editor command not all types are allowed to
* be passed as arguments. Allowed are the primitive types `string`, `boolean`,
* `number`, `undefined`, and `null`, as well as [`Position`](#Position), [`Range`](#Range), [`URI`](#URI) and [`Location`](#Location).
* * *Note 2:* There are no restrictions when executing commands that have been contributed
* by extensions.
*
* @param command Identifier of the command to execute.
* @param rest Parameters passed to the command function.
* @return A promise that resolves to the returned value of the given command. `undefined` when
* the command handler function doesn't return anything.
*/
public executeCommand<T>(command: string, ...rest: any[]): Promise<T> {
let cmd = this.commands.get(command)
if (!cmd) throw new Error(`Command: ${command} not found`)
return Promise.resolve(cmd.execute.apply(cmd, rest))
}
/**
* Used for user invoked command.
*/
public async fireCommand(id: string, ...args: any[]): Promise<unknown> {
// needed to load onCommand extensions
await events.fire('Command', [id])
let start = Date.now()
let res = await this.executeCommand(id, ...args)
if (args.length == 0) {
await this.addRecent(id, events.lastChangeTs > start)
}
return res
}
public async addRecent(cmd: string, repeat: boolean): Promise<void> {
await this.mru.add(cmd)
if (repeat) await workspace.nvim.command(`silent! call repeat#set("\\<Plug>(coc-command-repeat)", -1)`)
}
public async repeatCommand(): Promise<void> {
let mruList = await this.mru.load()
let first = mruList[0]
if (first) {
await this.executeCommand(first)
await workspace.nvim.command(`silent! call repeat#set("\\<Plug>(coc-command-repeat)", -1)`)
}
}
}
export default new CommandManager()