1
0
Fork 0
mirror of synced 2024-06-25 18:21:11 -04:00
ultimate-vim/sources_non_forked/coc.nvim/src/core/fileSystemWatcher.ts
2022-07-20 13:20:15 +08:00

198 lines
6.4 KiB
TypeScript

'use strict'
import minimatch from 'minimatch'
import path from 'path'
import { Disposable, Emitter, WorkspaceFolder, Event } from 'vscode-languageserver-protocol'
import { URI } from 'vscode-uri'
import { OutputChannel } from '../types'
import { disposeAll } from '../util'
import { splitArray } from '../util/array'
import Watchman, { FileChange } from './watchman'
import WorkspaceFolderControl from './workspaceFolder'
const logger = require('../util/logger')('filesystem-watcher')
export interface RenameEvent {
oldUri: URI
newUri: URI
}
export class FileSystemWatcherManager {
private clientsMap: Map<string, Watchman | null> = new Map()
private disposables: Disposable[] = []
private channel: OutputChannel | undefined
private creating: Set<string> = new Set()
public static watchers: Set<FileSystemWatcher> = new Set()
private readonly _onDidCreateClient = new Emitter<string>()
public readonly onDidCreateClient: Event<string> = this._onDidCreateClient.event
constructor(
private workspaceFolder: WorkspaceFolderControl,
private watchmanPath: string | null
) {
}
public attach(channel: OutputChannel): void {
this.channel = channel
let createClient = (folder: WorkspaceFolder) => {
let root = URI.parse(folder.uri).fsPath
if (this.creating.has(root)) return
this.creating.add(root)
this.createClient(root).finally(() => {
this.creating.delete(root)
})
}
this.workspaceFolder.workspaceFolders.forEach(folder => {
createClient(folder)
})
this.workspaceFolder.onDidChangeWorkspaceFolders(e => {
e.added.forEach(folder => {
createClient(folder)
})
e.removed.forEach(folder => {
let root = URI.parse(folder.uri).fsPath
let client = this.clientsMap.get(root)
if (client) {
this.clientsMap.delete(root)
client.dispose()
}
})
}, null, this.disposables)
}
public waitClient(root: string): Promise<void> {
if (this.clientsMap.has(root)) return Promise.resolve()
return new Promise(resolve => {
let disposable = this.onDidCreateClient(r => {
if (r == root) {
disposable.dispose()
resolve()
}
})
})
}
public async createClient(root: string): Promise<void> {
if (this.watchmanPath == null || this.clientsMap.has(root)) return
try {
let client = await Watchman.createClient(this.watchmanPath, root, this.channel)
if (!client) return
this.clientsMap.set(root, client)
for (let watcher of FileSystemWatcherManager.watchers) {
watcher.listen(client)
}
this._onDidCreateClient.fire(root)
} catch (e) {
if (this.channel) this.channel.appendLine(`Error on create watchman client:` + e)
}
}
public createFileSystemWatcher(
globPattern: string,
ignoreCreateEvents: boolean,
ignoreChangeEvents: boolean,
ignoreDeleteEvents: boolean): FileSystemWatcher {
let fileWatcher = new FileSystemWatcher(globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents)
for (let client of this.clientsMap.values()) {
fileWatcher.listen(client)
}
FileSystemWatcherManager.watchers.add(fileWatcher)
return fileWatcher
}
public dispose(): void {
this._onDidCreateClient.dispose()
for (let client of this.clientsMap.values()) {
if (client) client.dispose()
}
this.clientsMap.clear()
FileSystemWatcherManager.watchers.clear()
disposeAll(this.disposables)
}
}
/*
* FileSystemWatcher for watch workspace folders.
*/
export class FileSystemWatcher implements Disposable {
private _onDidCreate = new Emitter<URI>()
private _onDidChange = new Emitter<URI>()
private _onDidDelete = new Emitter<URI>()
private _onDidRename = new Emitter<RenameEvent>()
private disposables: Disposable[] = []
private _disposed = false
public subscribe: string
public readonly onDidCreate: Event<URI> = this._onDidCreate.event
public readonly onDidChange: Event<URI> = this._onDidChange.event
public readonly onDidDelete: Event<URI> = this._onDidDelete.event
public readonly onDidRename: Event<RenameEvent> = this._onDidRename.event
constructor(
private globPattern: string,
public ignoreCreateEvents: boolean,
public ignoreChangeEvents: boolean,
public ignoreDeleteEvents: boolean,
) {
}
public listen(client: Watchman): void {
let { globPattern,
ignoreCreateEvents,
ignoreChangeEvents,
ignoreDeleteEvents } = this
const onChange = (change: FileChange) => {
let { root, files } = change
files = files.filter(f => f.type == 'f' && minimatch(f.name, globPattern, { dot: true }))
for (let file of files) {
let uri = URI.file(path.join(root, file.name))
if (!file.exists) {
if (!ignoreDeleteEvents) this._onDidDelete.fire(uri)
} else {
if (file.new === true) {
if (!ignoreCreateEvents) this._onDidCreate.fire(uri)
} else {
if (!ignoreChangeEvents) this._onDidChange.fire(uri)
}
}
}
// file rename
if (files.length == 2 && !files[0].exists && files[1].exists) {
let oldFile = files[0]
let newFile = files[1]
if (oldFile.size == newFile.size) {
this._onDidRename.fire({
oldUri: URI.file(path.join(root, oldFile.name)),
newUri: URI.file(path.join(root, newFile.name))
})
}
}
// detect folder rename
if (files.length >= 2) {
let [oldFiles, newFiles] = splitArray(files, o => o.exists === false)
if (oldFiles.length == newFiles.length) {
for (let oldFile of oldFiles) {
let newFile = newFiles.find(o => o.size == oldFile.size && o.mtime_ms == oldFile.mtime_ms)
if (newFile) {
this._onDidRename.fire({
oldUri: URI.file(path.join(root, oldFile.name)),
newUri: URI.file(path.join(root, newFile.name))
})
}
}
}
}
}
client.subscribe(globPattern, onChange).then(disposable => {
this.subscribe = disposable.subscribe
if (this._disposed) return disposable.dispose()
this.disposables.push(disposable)
}).logError()
}
public dispose(): void {
this._disposed = true
FileSystemWatcherManager.watchers.delete(this)
this._onDidRename.dispose()
this._onDidCreate.dispose()
this._onDidChange.dispose()
disposeAll(this.disposables)
}
}