Skip to content

Commit

Permalink
feat(fileSystemWatch): add more configurations
Browse files Browse the repository at this point in the history
Added:
- fileSystemWatch.watchmanPath
- fileSystemWatch.enable
- fileSystemWatch.ignoredFolders
  • Loading branch information
chemzqm committed Feb 24, 2025
1 parent 3779dfc commit 5542116
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 67 deletions.
22 changes: 22 additions & 0 deletions data/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@
"coc.preferences.watchmanPath": {
"type": "string",
"scope": "application",
"deprecationMessage": "Use configuration \"fileSystemWatch.watchmanPath\" instead.",
"description": "executable path for https://facebook.github.io/watchman/, detected from $PATH by default",
"default": null
},
Expand Down Expand Up @@ -1261,6 +1262,27 @@
"scope": "resource",
"description": "Timeout for document highlight, in milliseconds."
},
"fileSystemWatch.watchmanPath": {
"type": ["null", "string"],
"scope": "application",
"description": "executable path for https://facebook.github.io/watchman/, detected from $PATH by default",
"default": null
},
"fileSystemWatch.enable": {
"type": "boolean",
"default": true,
"scope": "application",
"description": "Enable file system watch support for workspace folders."
},
"fileSystemWatch.ignoredFolders": {
"type": "array",
"default": ["/private/tmp", "/", "${tmpdir}"],
"scope": "application",
"description": "List of folders that should not be watched for file changes, environment variables and minimatch patterns can be used.",
"items": {
"type": "string"
}
},
"floatFactory.floatConfig": {
"type": "object",
"scope": "application",
Expand Down
25 changes: 25 additions & 0 deletions doc/coc-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CONTENTS

Core features
Workspace |coc-config-workspace|
File system watch |coc-config-fileSystemWatch|
Extensions |coc-config-extensions|
Preferences |coc-config-preferences|
Float factory |coc-config-floatFactory|
Expand Down Expand Up @@ -103,6 +104,30 @@ WORKSPACE

Scope: `application`, default: `true`

------------------------------------------------------------------------------
FILESYSTEMWATCH *coc-config-fileSystemWatch*

"fileSystemWatch.watchmanPath" *coc-config-filesystemwatch-watchmanPath*

executable path for https://facebook.github.io/watchman/, detected
from $PATH by default

Scope: `application`, default: `null`

"fileSystemWatch.enable" *coc-config-filesystemwatch-enable*

Enable file system watch support for workspace folders.

Scope: `application`, default: `true`

"fileSystemWatch.ignoredFolders" *coc-config-filesystemwatch-ignoredFolders*

List of folders that should not be watched for file changes,
environment variables and minimatch patterns
can be used.

Scope: `application`, default: `["/private/tmp", "/", "${tmpdir}"]`

------------------------------------------------------------------------------
EXTENSIONS
*coc-config-extensions*
Expand Down
22 changes: 22 additions & 0 deletions doc/coc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CONTENTS *coc-contents*
Introduction |coc-introduction|
Requirements |coc-requirements|
Installation |coc-installation|
File system watch |coc-filesystemwatch|
Language server |coc-languageserver|
Extensions |coc-extensions|
Configuration |coc-configuration|
Expand Down Expand Up @@ -146,6 +147,25 @@ To use Vim's native |packages| on Linux or macOS, use script like: >
when using source code of coc.nvim, you'll have to run `npm install` in project
root of coc.nvim.

==============================================================================
File system watch *coc-filesystemwatch*

Watchman https://facebook.github.io/watchman/ is used by coc.nvim to provide
file change detection to extensions and languageservers. The watchman command
is detected from your $PATH, the feature will silently fail when watchman
can't work.

Watchman automatically watch |coc-workspace-folders| for file events by
default.

Use command: >
:CocCommand workspace.showOutput watchman
<
to open output channel of watchman.

Use configuration |coc-config-fileSystemWatch| to change behavior of file
system watch.

==============================================================================
LANGUAGESERVER *coc-languageserver*

Expand Down Expand Up @@ -674,6 +694,8 @@ Note most language servers only send diagnostics for opened buffers for
performance reason, some lint tools could provide diagnostics for all files in
workspace.

See |coc-highlights-diagnostics| for diagnostic related highlight groups.

*coc-diagnostics-refresh*
Changes on diagnostics refresh ~

Expand Down
36 changes: 13 additions & 23 deletions src/__tests__/core/fileSystemWatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { v4 as uuid } from 'uuid'
import { Disposable } from 'vscode-languageserver-protocol'
import { URI } from 'vscode-uri'
import Configurations from '../../configuration/index'
import { FileSystemWatcher, FileSystemWatcherManager } from '../../core/fileSystemWatcher'
import Watchman, { FileChangeItem, isValidWatchRoot } from '../../core/watchman'
import { FileSystemWatcher, FileSystemWatcherManager, should_ignore } from '../../core/fileSystemWatcher'
import Watchman, { FileChangeItem } from '../../core/watchman'
import WorkspaceFolderController from '../../core/workspaceFolder'
import RelativePattern from '../../model/relativePattern'
import { GlobPattern } from '../../types'
Expand Down Expand Up @@ -59,6 +59,7 @@ function sendSubscription(uid: string, root: string, files: FileChangeItem[]): v

let capabilities: any
let watchResponse: any
let defaultConfig = { watchmanPath: null, enable: true, ignoredFolders: [] }
beforeAll(async () => {
await helper.setup()
})
Expand All @@ -67,7 +68,7 @@ beforeAll(done => {
let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')
configurations = new Configurations(userConfigFile, undefined)
workspaceFolder = new WorkspaceFolderController(configurations)
watcherManager = new FileSystemWatcherManager(workspaceFolder, 'watchman')
watcherManager = new FileSystemWatcherManager(workspaceFolder, defaultConfig)
Object.assign(watcherManager, { disabled: false })
watcherManager.attach(helper.createNullChannel())
// create a mock sever for watchman
Expand Down Expand Up @@ -235,21 +236,6 @@ describe('Watchman#createClient', () => {
expect(client).toBeDefined()
})

it('should not create client for root', async () => {
await expect(async () => {
await Watchman.createClient(null, '/')
}).rejects.toThrow(Error)
})
})

describe('isValidWatchRoot()', () => {
it('should check valid root', async () => {
expect(isValidWatchRoot('/')).toBe(false)
expect(isValidWatchRoot(os.homedir())).toBe(false)
expect(isValidWatchRoot(os.tmpdir())).toBe(false)
expect(isValidWatchRoot('/tmp/a')).toBe(true)
expect(isValidWatchRoot('/tmp/a/b/c')).toBe(true)
})
})

describe('fileSystemWatcher', () => {
Expand Down Expand Up @@ -278,6 +264,11 @@ describe('fileSystemWatcher', () => {
await watcherManager.waitClient(cwd)
})

it('should check ignored folders', async () => {
expect(should_ignore('/', [])).toBe(false)
expect(should_ignore('/', ['/', 'a'])).toBe(true)
})

it('should use relative pattern #1', async () => {
let folder = workspaceFolder.workspaceFolders[0]
expect(folder).toBeDefined()
Expand Down Expand Up @@ -428,7 +419,7 @@ describe('fileSystemWatcher', () => {
watcher.onDidCreate(e => {
uri = e
})
await helper.wait(50)
await waitReady(watcher)
let changes: FileChangeItem[] = [createFileChange(`a`)]
sendSubscription(watcher.subscribe, __dirname, changes)
await helper.waitValue(() => {
Expand All @@ -441,17 +432,16 @@ describe('create FileSystemWatcherManager', () => {
it('should attach to existing workspace folder', async () => {
let workspaceFolder = new WorkspaceFolderController(configurations)
workspaceFolder.addWorkspaceFolder(cwd, false)
let watcherManager = new FileSystemWatcherManager(workspaceFolder, '')
Object.assign(watcherManager, { disabled: false })
let watcherManager = new FileSystemWatcherManager(workspaceFolder, { ...defaultConfig, enable: false })
watcherManager.disabled = false
watcherManager.attach(helper.createNullChannel())
await watcherManager.createClient(os.tmpdir())
await watcherManager.createClient(cwd)
await watcherManager.waitClient(cwd)
watcherManager.dispose()
})

it('should get watchman path', async () => {
let watcherManager = new FileSystemWatcherManager(workspaceFolder, 'invalid_command')
let watcherManager = new FileSystemWatcherManager(workspaceFolder, { ...defaultConfig, watchmanPath: 'invalid_command' })
process.env.WATCHMAN_SOCK = ''
await expect(async () => {
await watcherManager.getWatchmanPath()
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/modules/extensionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,8 @@ describe('ExtensionManager', () => {
let manager = create(tmpfolder)
let res = await manager.loadExtension(extFolder)
expect(res).toBe(true)
let spy = jest.spyOn(workspace, 'getWatchmanPath').mockImplementation(() => {
return ''
let spy = jest.spyOn(workspace.fileSystemWatchers, 'getWatchmanPath').mockImplementation(() => {
return Promise.reject('not found')
})
let fn = async () => {
await manager.watchExtension('name')
Expand All @@ -659,6 +659,7 @@ describe('ExtensionManager', () => {

it('should reload extension on file change', async () => {
tmpfolder = createFolder()
workspace.fileSystemWatchers.disabled = false
let extFolder = path.join(tmpfolder, 'node_modules', 'name')
createExtension(extFolder, { name: 'name', main: 'entry.js', engines: { coc: '>=0.0.1' } })
let manager = create(tmpfolder)
Expand Down
2 changes: 2 additions & 0 deletions src/configuration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export function expand(input: string): string {
return process.env[key] ?? match
}
switch (name) {
case 'tmpdir':
return os.tmpdir()
case 'userHome':
return os.homedir()
case 'cwd':
Expand Down
2 changes: 2 additions & 0 deletions src/core/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ export default class Documents implements Disposable {
return val
}
switch (name) {
case 'tmpdir':
return os.tmpdir()
case 'userHome':
return os.homedir()
case 'workspace':
Expand Down
42 changes: 29 additions & 13 deletions src/core/fileSystemWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,47 @@
import { WorkspaceFolder } from 'vscode-languageserver-types'
import { URI } from 'vscode-uri'
import { createLogger } from '../logger'
import { GlobPattern, IFileSystemWatcher, OutputChannel } from '../types'
import { FileWatchConfig, GlobPattern, IFileSystemWatcher, OutputChannel } from '../types'
import { disposeAll } from '../util'
import { splitArray } from '../util/array'
import { isParentFolder, sameFile } from '../util/fs'
import { minimatch, path, which } from '../util/node'
import { minimatch, path, which, os } from '../util/node'
import { Disposable, Emitter, Event } from '../util/protocol'
import Watchman, { FileChange } from './watchman'
import type WorkspaceFolderControl from './workspaceFolder'
const logger = createLogger('fileSystemWatcher')
const WATCHMAN_COMMAND = 'watchman'

export interface RenameEvent {
oldUri: URI
newUri: URI
}

export function should_ignore(root: string, ignored: string[] | undefined): boolean {
for (let folder of ignored) {
if (sameFile(root, folder)) {
return true
}
}
return false
}

export class FileSystemWatcherManager {
private clientsMap: Map<string, Watchman | null> = new Map()
private clientsMap: Map<string, Watchman> = 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>()
private disabled = global.__TEST__
public disabled = global.__TEST__
public readonly onDidCreateClient: Event<string> = this._onDidCreateClient.event
constructor(
private workspaceFolder: WorkspaceFolderControl,
private watchmanPath: string | null
private config: FileWatchConfig
) {
if (!config.enable) {
this.disabled = true
}
}

public attach(channel: OutputChannel): void {
Expand All @@ -56,40 +69,43 @@ export class FileSystemWatcherManager {
}, null, this.disposables)
}

public waitClient(root: string): Promise<void> {
if (this.clientsMap.has(root)) return Promise.resolve()
public waitClient(root: string): Promise<Watchman> {
if (this.clientsMap.has(root)) return Promise.resolve(this.clientsMap.get(root))
return new Promise(resolve => {
let disposable = this.onDidCreateClient(r => {
if (r == root) {
disposable.dispose()
resolve()
resolve(this.clientsMap.get(r))
}
})
})
}

public async createClient(root: string): Promise<void> {
if (this.watchmanPath == null || this.has(root) || this.disabled) return
public async createClient(root: string, skipCheck = false): Promise<Watchman | false | undefined> {
if (!skipCheck && (this.disabled || should_ignore(root, this.config.ignoredFolders))) return
if (this.has(root)) return this.waitClient(root)
try {
let watchmanPath = await this.getWatchmanPath()
this.creating.add(root)
let watchmanPath = await this.getWatchmanPath()
let client = await Watchman.createClient(watchmanPath, root, this.channel)
this.creating.delete(root)
this.clientsMap.set(root, client)
for (let watcher of FileSystemWatcherManager.watchers) {
watcher.listen(root, client)
}
this._onDidCreateClient.fire(root)
return client
} catch (e) {
this.creating.delete(root)
if (this.channel) this.channel.appendLine(`Error on create watchman client: ${e}`)
return false
}
}

public async getWatchmanPath(): Promise<string> {
let watchmanPath = this.watchmanPath
let watchmanPath = this.config.watchmanPath ?? WATCHMAN_COMMAND
if (!process.env.WATCHMAN_SOCK) {
watchmanPath = await which(this.watchmanPath, { all: false })
watchmanPath = await which(watchmanPath, { all: false })
}
return watchmanPath
}
Expand Down
Loading

0 comments on commit 5542116

Please sign in to comment.