1
0
Fork 0
mirror of synced 2024-06-02 23:41:09 -04:00
ultimate-vim/sources_non_forked/coc.nvim/src/workspace.ts
2022-07-05 14:25:26 +08:00

553 lines
18 KiB
TypeScript

'use strict'
import { NeovimClient as Neovim } from '@chemzqm/neovim'
import fs from 'fs-extra'
import os from 'os'
import path from 'path'
import { CancellationToken, CreateFileOptions, DeleteFileOptions, Disposable, DocumentSelector, Event, FormattingOptions, Location, LocationLink, Position, RenameFileOptions, WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersChangeEvent } from 'vscode-languageserver-protocol'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { URI } from 'vscode-uri'
import { version as VERSION } from '../package.json'
import Configurations from './configuration'
import ConfigurationShape from './configuration/shape'
import Autocmds from './core/autocmds'
import channels from './core/channels'
import ContentProvider from './core/contentProvider'
import Documents from './core/documents'
import Files, { GlobPattern } from './core/files'
import { FileSystemWatcher, FileSystemWatcherManager } from './core/fileSystemWatcher'
import { createNameSpace, findUp, getWatchmanPath, has, resolveModule, score } from './core/funcs'
import Keymaps from './core/keymaps'
import Locations from './core/locations'
import * as ui from './core/ui'
import Watchers from './core/watchers'
import Editors from './core/editors'
import WorkspaceFolderController from './core/workspaceFolder'
import events from './events'
import BufferSync, { SyncItem } from './model/bufferSync'
import DB from './model/db'
import type Document from './model/document'
import Mru from './model/mru'
import Task from './model/task'
import { LinesTextDocument } from './model/textdocument'
import { TextDocumentContentProvider } from './provider'
import { Autocmd, ConfigurationChangeEvent, ConfigurationTarget, DidChangeTextDocumentParams, EditerState, Env, FileCreateEvent, FileDeleteEvent, FileRenameEvent, FileWillCreateEvent, FileWillDeleteEvent, FileWillRenameEvent, IWorkspace, KeymapOption, LocalMode, QuickfixItem, TextDocumentWillSaveEvent, WorkspaceConfiguration } from './types'
import { CONFIG_FILE_NAME, MapMode, runCommand } from './util/index'
const APIVERSION = 30
const logger = require('./util/logger')('workspace')
const methods = [
'showMessage', 'runTerminalCommand', 'openTerminal', 'showQuickpick',
'menuPick', 'openLocalConfig', 'showPrompt', 'createStatusBarItem', 'createOutputChannel',
'showOutputChannel', 'requestInput', 'echoLines', 'getCursorPosition', 'moveTo',
'getOffset', 'getSelectedRange', 'selectRange', 'createTerminal',
]
export class Workspace implements IWorkspace {
public readonly onDidChangeConfiguration: Event<ConfigurationChangeEvent>
public readonly onDidOpenTextDocument: Event<LinesTextDocument & { bufnr: number }>
public readonly onDidCloseTextDocument: Event<LinesTextDocument & { bufnr: number }>
public readonly onDidChangeTextDocument: Event<DidChangeTextDocumentParams>
public readonly onDidSaveTextDocument: Event<LinesTextDocument>
public readonly onWillSaveTextDocument: Event<TextDocumentWillSaveEvent>
public readonly onDidChangeWorkspaceFolders: Event<WorkspaceFoldersChangeEvent>
public readonly onDidRuntimePathChange: Event<string[]>
public readonly onDidCreateFiles: Event<FileCreateEvent>
public readonly onDidRenameFiles: Event<FileRenameEvent>
public readonly onDidDeleteFiles: Event<FileDeleteEvent>
public readonly onWillCreateFiles: Event<FileWillCreateEvent>
public readonly onWillRenameFiles: Event<FileWillRenameEvent>
public readonly onWillDeleteFiles: Event<FileWillDeleteEvent>
public readonly nvim: Neovim
public readonly version: string
public readonly configurations: Configurations
public readonly workspaceFolderControl: WorkspaceFolderController
public readonly documentsManager: Documents
public readonly contentProvider: ContentProvider
public readonly autocmds: Autocmds
public readonly watchers: Watchers
public readonly keymaps: Keymaps
public readonly locations: Locations
public readonly files: Files
public readonly fileSystemWatchers: FileSystemWatcherManager
public readonly editors: Editors
private _env: Env
constructor() {
this.version = VERSION
let home = path.normalize(process.env.COC_VIMCONFIG) || path.join(os.homedir(), '.vim')
let userConfigFile = path.join(home, CONFIG_FILE_NAME)
this.configurations = new Configurations(userConfigFile, new ConfigurationShape(this))
this.workspaceFolderControl = new WorkspaceFolderController(this.configurations)
let documents = this.documentsManager = new Documents(this.configurations, this.workspaceFolderControl)
this.contentProvider = new ContentProvider(documents)
this.watchers = new Watchers()
this.autocmds = new Autocmds(this.contentProvider, this.watchers)
this.keymaps = new Keymaps(documents)
this.locations = new Locations(this.configurations, documents, this.contentProvider)
this.files = new Files(documents, this.configurations, this.workspaceFolderControl, this.keymaps)
this.editors = new Editors(documents)
this.onDidRuntimePathChange = this.watchers.onDidRuntimePathChange
this.onDidChangeWorkspaceFolders = this.workspaceFolderControl.onDidChangeWorkspaceFolders
this.onDidChangeConfiguration = this.configurations.onDidChange
this.onDidOpenTextDocument = documents.onDidOpenTextDocument
this.onDidChangeTextDocument = documents.onDidChangeDocument
this.onDidCloseTextDocument = documents.onDidCloseDocument
this.onDidSaveTextDocument = documents.onDidSaveTextDocument
this.onWillSaveTextDocument = documents.onWillSaveTextDocument
this.onDidCreateFiles = this.files.onDidCreateFiles
this.onDidRenameFiles = this.files.onDidRenameFiles
this.onDidDeleteFiles = this.files.onDidDeleteFiles
this.onWillCreateFiles = this.files.onWillCreateFiles
this.onWillRenameFiles = this.files.onWillRenameFiles
this.onWillDeleteFiles = this.files.onWillDeleteFiles
let watchmanPath = global.__TEST__ ? null : this.getWatchmanPath()
this.fileSystemWatchers = new FileSystemWatcherManager(this.workspaceFolderControl, watchmanPath)
}
public async init(window: any): Promise<void> {
let { nvim } = this
for (let method of methods) {
Object.defineProperty(this, method, {
get: () => {
return (...args: any[]) => {
let stack = '\n' + Error().stack.split('\n').slice(2, 4).join('\n')
logger.warn(`workspace.${method} is deprecated, please use window.${method} instead.`, stack)
return window[method].apply(window, args)
}
}
})
}
for (let name of ['onDidOpenTerminal', 'onDidCloseTerminal']) {
Object.defineProperty(this, name, {
get: () => {
let stack = '\n' + Error().stack.split('\n').slice(2, 4).join('\n')
logger.warn(`workspace.${name} is deprecated, please use window.${name} instead.`, stack)
return window[name]
}
})
}
let env = this._env = await nvim.call('coc#util#vim_info') as Env
window.init(env)
if (this._env.apiversion != APIVERSION) {
nvim.echoError(`API version ${this._env.apiversion} is not ${APIVERSION}, please build coc.nvim by 'yarn install' after pull source code.`)
}
this.workspaceFolderControl.setWorkspaceFolders(this._env.workspaceFolders)
this.configurations.updateUserConfig(this._env.config)
this.files.attach(nvim, env, window)
this.contentProvider.attach(nvim)
this.keymaps.attach(nvim)
this.autocmds.attach(nvim, env)
this.locations.attach(nvim, env)
this.watchers.attach(nvim, env)
await this.attach()
await this.editors.attach(nvim)
let channel = channels.create('watchman', nvim)
this.fileSystemWatchers.attach(channel)
}
public get cwd(): string {
return this.documentsManager.cwd
}
public get env(): Env {
return this._env
}
public get root(): string {
return this.documentsManager.root || this.cwd
}
public get rootPath(): string {
return this.root
}
public get bufnr(): number {
return this.documentsManager.bufnr
}
/**
* @deprecated
*/
public get insertMode(): boolean {
return events.insertMode
}
public get floatSupported(): boolean {
return this.env.floating || this.env.textprop
}
/**
* @deprecated
*/
public get uri(): string {
return this.documentsManager.uri
}
/**
* @deprecated
*/
public get workspaceFolder(): WorkspaceFolder {
return this.workspaceFolders[0]
}
public get textDocuments(): TextDocument[] {
return this.documentsManager.textDocuments
}
public get documents(): Document[] {
return this.documentsManager.documents
}
public get document(): Promise<Document | undefined> {
return this.documentsManager.document
}
public get workspaceFolders(): ReadonlyArray<WorkspaceFolder> {
return this.workspaceFolderControl.workspaceFolders
}
public get folderPaths(): string[] {
return this.workspaceFolders.map(f => URI.parse(f.uri).fsPath)
}
public get channelNames(): string[] {
return channels.names
}
public get pluginRoot(): string {
return path.dirname(__dirname)
}
public get isVim(): boolean {
return this._env.isVim
}
public get isNvim(): boolean {
return !this._env.isVim
}
public get completeOpt(): string {
return this._env.completeOpt
}
public get filetypes(): Set<string> {
return this.documentsManager.filetypes
}
public get languageIds(): Set<string> {
return this.documentsManager.languageIds
}
/**
* @deprecated
*/
public createNameSpace(name: string): number {
return createNameSpace(name)
}
public getConfigFile(target: ConfigurationTarget): string {
return this.configurations.getConfigFile(target)
}
public has(feature: string): boolean {
return has(this.env, feature)
}
/**
* Register autocmd on vim.
*/
public registerAutocmd(autocmd: Autocmd): Disposable {
return this.autocmds.registerAutocmd(autocmd)
}
/**
* Watch for option change.
*/
public watchOption(key: string, callback: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): void {
this.watchers.watchOption(key, callback, disposables)
}
/**
* Watch global variable, works on neovim only.
*/
public watchGlobal(key: string, callback?: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): void {
this.watchers.watchGlobal(key, callback || function() {}, disposables)
}
/**
* Check if selector match document.
*/
public match(selector: DocumentSelector, document: { uri: string, languageId: string }): number {
return score(selector, document.uri, document.languageId)
}
/**
* Create a FileSystemWatcher instance, doesn't fail when watchman not found.
*/
public createFileSystemWatcher(globPattern: string, ignoreCreate?: boolean, ignoreChange?: boolean, ignoreDelete?: boolean): FileSystemWatcher {
return this.fileSystemWatchers.createFileSystemWatcher(globPattern, ignoreCreate, ignoreChange, ignoreDelete)
}
public getWatchmanPath(): string | null {
return getWatchmanPath(this.configurations)
}
/**
* Get configuration by section and optional resource uri.
*/
public getConfiguration(section?: string, resource?: string): WorkspaceConfiguration {
return this.configurations.getConfiguration(section, resource)
}
/**
* Get created document by uri or bufnr.
*/
public getDocument(uri: number | string): Document | null {
return this.documentsManager.getDocument(uri)
}
public isAttached(bufnr: number): boolean {
let doc = this.documentsManager.getDocument(bufnr)
return doc != null && doc.attached
}
/**
* Get attached document by uri or bufnr.
* Throw error when document doesn't exist or isn't attached.
*/
public getAttachedDocument(uri: number | string): Document {
let doc = this.getDocument(uri)
if (!doc) throw new Error(`Buffer ${uri} not created.`)
if (!doc.attached) throw new Error(`Buffer ${uri} not attached, try :CocCommand document.checkBuffer`)
return doc
}
/**
* Convert location to quickfix item.
*/
public getQuickfixItem(loc: Location | LocationLink, text?: string, type = '', module?: string): Promise<QuickfixItem> {
return this.documentsManager.getQuickfixItem(loc, text, type, module)
}
/**
* Create persistence Mru instance.
*/
public createMru(name: string): Mru {
return new Mru(name)
}
public async getQuickfixList(locations: Location[]): Promise<ReadonlyArray<QuickfixItem>> {
return this.documentsManager.getQuickfixList(locations)
}
/**
* Populate locations to UI.
*/
public async showLocations(locations: Location[]): Promise<void> {
await this.locations.showLocations(locations)
}
/**
* Get content of line by uri and line.
*/
public getLine(uri: string, line: number): Promise<string> {
return this.documentsManager.getLine(uri, line)
}
/**
* Get WorkspaceFolder of uri
*/
public getWorkspaceFolder(uri: string): WorkspaceFolder | undefined {
return this.workspaceFolderControl.getWorkspaceFolder(URI.parse(uri))
}
/**
* Get content from buffer or file by uri.
*/
public readFile(uri: string): Promise<string> {
return this.documentsManager.readFile(uri)
}
public async getCurrentState(): Promise<EditerState> {
let document = await this.document
let position = await ui.getCursorPosition(this.nvim)
return {
document: document.textDocument,
position
}
}
public async getFormatOptions(uri?: string): Promise<FormattingOptions> {
return this.documentsManager.getFormatOptions(uri)
}
/**
* Resolve module from yarn or npm.
*/
public resolveModule(name: string): Promise<string> {
return resolveModule(name)
}
/**
* Run nodejs command
*/
public async runCommand(cmd: string, cwd?: string, timeout?: number): Promise<string> {
cwd = cwd || this.cwd
return runCommand(cmd, { cwd }, timeout)
}
/**
* Expand filepath with `~` and/or environment placeholders
*/
public expand(filepath: string): string {
return this.documentsManager.expand(filepath)
}
public async callAsync<T>(method: string, args: any[]): Promise<T> {
if (this.isNvim) return await this.nvim.call(method, args)
return await this.nvim.callAsync('coc#util#with_callback', [method, args])
}
public registerTextDocumentContentProvider(scheme: string, provider: TextDocumentContentProvider): Disposable {
return this.contentProvider.registerTextDocumentContentProvider(scheme, provider)
}
public registerKeymap(modes: MapMode[], key: string, fn: Function, opts: Partial<KeymapOption> = {}): Disposable {
return this.keymaps.registerKeymap(modes, key, fn, opts)
}
public registerExprKeymap(mode: 'i' | 'n' | 'v' | 's' | 'x', key: string, fn: Function, buffer = false): Disposable {
return this.keymaps.registerExprKeymap(mode, key, fn, buffer)
}
public registerLocalKeymap(mode: LocalMode, key: string, fn: Function, notify = false): Disposable {
return this.keymaps.registerLocalKeymap(mode, key, fn, notify)
}
/**
* Create Task instance that runs in vim.
*/
public createTask(id: string): Task {
return new Task(this.nvim, id)
}
/**
* Create DB instance at extension root.
*/
public createDatabase(name: string): DB {
let root: string
if (global.hasOwnProperty('__TEST__')) {
root = path.join(os.tmpdir(), `coc-${process.pid}`)
fs.mkdirpSync(root)
} else {
root = path.dirname(this.env.extensionRoot)
}
let filepath = path.join(root, name + '.json')
return new DB(filepath)
}
public registerBufferSync<T extends SyncItem>(create: (doc: Document) => T | undefined): BufferSync<T> {
return new BufferSync(create, this.documentsManager)
}
public async attach(): Promise<void> {
await this.documentsManager.attach(this.nvim, this._env)
}
public jumpTo(uri: string, position?: Position | null, openCommand?: string): Promise<void> {
return this.files.jumpTo(uri, position, openCommand)
}
/**
* Findup for filename or filenames from current filepath or root.
*/
public findUp(filename: string | string[]): Promise<string | null> {
return findUp(this.nvim, this.cwd, filename)
}
/**
* Apply WorkspaceEdit.
*/
public applyEdit(edit: WorkspaceEdit): Promise<boolean> {
return this.files.applyEdit(edit)
}
/**
* Create a file in vim and disk
*/
public createFile(filepath: string, opts: CreateFileOptions = {}): Promise<void> {
return this.files.createFile(filepath, opts)
}
/**
* Load uri as document.
*/
public loadFile(uri: string): Promise<Document> {
return this.files.loadResource(uri)
}
/**
* Load the files that not loaded
*/
public async loadFiles(uris: string[]): Promise<(Document | undefined)[]> {
return this.files.loadResources(uris)
}
/**
* Rename file in vim and disk
*/
public async renameFile(oldPath: string, newPath: string, opts: RenameFileOptions = {}): Promise<void> {
await this.files.renameFile(oldPath, newPath, opts)
}
/**
* Delete file from vim and disk.
*/
public async deleteFile(filepath: string, opts: DeleteFileOptions = {}): Promise<void> {
await this.files.deleteFile(filepath, opts)
}
public async renameCurrent(): Promise<void> {
await this.files.renameCurrent()
}
/**
* Open resource by uri
*/
public async openResource(uri: string): Promise<void> {
await this.files.openResource(uri)
}
public openTextDocument(uri: URI | string): Promise<Document> {
return this.files.openTextDocument(uri)
}
public getRelativePath(pathOrUri: string | URI, includeWorkspace?: boolean): string {
return this.workspaceFolderControl.getRelativePath(pathOrUri, includeWorkspace)
}
public async findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Promise<URI[]> {
return this.files.findFiles(include, exclude, maxResults, token)
}
public detach(): void {
this.documentsManager.detach()
}
public reset(): void {
this.configurations.reset()
this.workspaceFolderControl.reset()
this.documentsManager.reset()
}
public dispose(): void {
this.watchers.dispose()
this.autocmds.dispose()
this.contentProvider.dispose()
this.documentsManager.dispose()
this.configurations.dispose()
}
}
export default new Workspace()