Skip to content

Commit

Permalink
fix(autocmds): use unique group for each autocmd
Browse files Browse the repository at this point in the history
  • Loading branch information
chemzqm committed Feb 21, 2025
1 parent 7f81235 commit e7abca2
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 33 deletions.
13 changes: 13 additions & 0 deletions autoload/coc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ function! coc#expandableOrJumpable() abort
return coc#rpc#request('snippetCheck', [1, 1])
endfunction

function! coc#clearGroups(prefix) abort
" 遍历所有 augroup 名称
for group in getcompletion('', 'augroup')
" 检查是否以指定前缀开头
if group =~# '^' . a:prefix
" 进入该 augroup,执行清空操作
execute 'augroup ' . group
autocmd!
augroup END
endif
endfor
endfunction

" add vim command to CocCommand list
function! coc#add_command(id, cmd, ...)
let config = {'id':a:id, 'cmd':a:cmd, 'title': get(a:,1,'')}
Expand Down
4 changes: 1 addition & 3 deletions autoload/coc/rpc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,7 @@ function! coc#rpc#restart()
call coc#highlight#clear_all()
call coc#ui#sign_unplace()
call coc#float#close_all()
autocmd! coc_dynamic_autocmd
autocmd! coc_dynamic_content
autocmd! coc_dynamic_option
call coc#clearGroups('coc_dynamic_')
call coc#rpc#request('detach', [])
if !empty(get(g:, 'coc_status', ''))
unlet g:coc_status
Expand Down
2 changes: 1 addition & 1 deletion doc/coc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2957,7 +2957,7 @@ COMMANDS *coc-commands*

To disable dynamic autocmds registered by extensions, use:
>
:autocmd! coc_dynamic_autocmd
:call coc#clearGroups('coc_dynamic_')
<
:CocEnable *:CocEnable*

Expand Down
2 changes: 0 additions & 2 deletions plugin/coc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -699,8 +699,6 @@ command! -nargs=0 -bar CocUpdateSync :call coc#util#update_extensions()
command! -nargs=* -bar -complete=custom,s:InstallOptions CocInstall :call coc#util#install_extension([<f-args>])

call s:Enable(1)
augroup coc_dynamic_autocmd
augroup END
augroup coc_dynamic_content
augroup END
augroup coc_dynamic_option
Expand Down
40 changes: 31 additions & 9 deletions src/__tests__/core/autocmds.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Neovim } from '../../neovim'
import { Disposable, Emitter } from 'vscode-languageserver-protocol'
import { URI } from 'vscode-uri'
import { createCommand } from '../../core/autocmds'
import { createCommand, getAutoCmdText } from '../../core/autocmds'
import events from '../../events'
import { TextDocumentContentProvider } from '../../provider'
import { disposeAll } from '../../util'
import workspace from '../../workspace'
import helper from '../helper'
import { getEnd } from '../../util/position'

let nvim: Neovim
let disposables: Disposable[] = []
Expand Down Expand Up @@ -113,12 +114,34 @@ describe('contentProvider', () => {
})

describe('setupDynamicAutocmd()', () => {
it('should get autocmd text', async () => {
let text = getAutoCmdText({
event: 'BufWinLeave',
callback: function foo() {}
})
expect(text).toBe('BufWinLeave function foo() {}')
text = getAutoCmdText({
event: ['BufWinLeave', 'BufEnter'],
arglist: ['arg'],
callback: function foo() {}
})
expect(text).toBe('BufWinLeave BufEnter arg function foo() {}')
text = getAutoCmdText({
event: 'BufWinLeave',
arglist: ['a', 'b'],
pattern: '*',
request: true,
callback: function bar() {}
})
expect(text).toBe('BufWinLeave * request a b function bar() {}')
})

it('should create command', async () => {
let callback = () => {}
expect(createCommand(1, { callback, event: 'event', arglist: [], pattern: '*', request: true })).toMatch('event')
expect(createCommand(1, { callback, event: 'event', arglist: ['foo'] })).toMatch('foo')
expect(createCommand(1, { callback, event: ['foo', 'bar'], arglist: [] })).toMatch('foo')
expect(createCommand(1, { callback, event: 'user Event', arglist: [] })).toMatch('user')
expect(createCommand('1', { callback, event: 'event', arglist: [], pattern: '*', request: true })).toMatch('event')
expect(createCommand('1', { callback, event: 'event', arglist: ['foo'] })).toMatch('foo')
expect(createCommand('1', { callback, event: ['foo', 'bar'], arglist: [] })).toMatch('foo')
expect(createCommand('1', { callback, event: 'user Event', arglist: [] })).toMatch('user')
})

it('should setup autocmd on vim', async () => {
Expand All @@ -131,7 +154,7 @@ describe('setupDynamicAutocmd()', () => {
called = true
}
})
await helper.wait(10)
await helper.wait(50)
await nvim.command('normal! $')
await helper.waitValue(() => called, true)
expect(called).toBe(true)
Expand All @@ -147,16 +170,15 @@ describe('setupDynamicAutocmd()', () => {
called = true
}
})
workspace.autocmds.resetDynamicAutocmd()
await helper.wait(10)
await helper.wait(50)
await nvim.command('doautocmd <nomodeline> User CocJumpPlaceholder')
await helper.waitValue(() => called, true)
})
})

describe('doAutocmd()', () => {
it('should not throw when command id does not exist', async () => {
await workspace.autocmds.doAutocmd(999, [])
await workspace.autocmds.doAutocmd('999', [])
})

it('should dispose', async () => {
Expand Down
52 changes: 36 additions & 16 deletions src/core/autocmds.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,89 @@
'use strict'
import { Neovim } from '../neovim'
import { Autocmd } from '../types'
import { disposeAll } from '../util'
import { isFalsyOrEmpty } from '../util/array'
import { Disposable } from '../util/protocol'
import { crypto } from '../util/node'

interface PartialEnv {
isCygwin: boolean
isVim: boolean
version: string
}

let autocmdMaxId = 0
const groupName = 'coc_dynamic_autocmd'
const groupPrefix = 'coc_dynamic_'

export function getAutoCmdText(autocmd: Autocmd): string {
let { arglist, pattern, request, callback } = autocmd
let res = ''
res += Array.isArray(autocmd.event) ? autocmd.event.join(' ') + ' ' : autocmd.event + ' '
if (pattern) res += pattern + ' '
if (request) res += 'request '
if (Array.isArray(arglist)) res += arglist.join(' ') + ' '
res += callback.toString()
return res
}

export default class Autocmds implements Disposable {
public readonly autocmds: Map<number, Autocmd> = new Map()
public readonly autocmds: Map<string, Autocmd> = new Map()
private nvim: Neovim
private env: PartialEnv
private disposables: Disposable[] = []

public attach(nvim: Neovim, env: PartialEnv): void {
this.nvim = nvim
this.env = env
}

public async doAutocmd(id: number, args: any[]): Promise<void> {
// unique id for autocmd to create unique group name
public generateId(autocmd: Autocmd): string {
let text = getAutoCmdText(autocmd)
return groupPrefix + crypto.createHash('md5').update(text).digest('hex')
}

public async doAutocmd(id: string, args: any[]): Promise<void> {
let autocmd = this.autocmds.get(id)
if (autocmd) await Promise.resolve(autocmd.callback.apply(autocmd.thisArg, args))
}

public registerAutocmd(autocmd: Autocmd): Disposable {
autocmdMaxId += 1
let id = autocmdMaxId
// Used as group name as well
let id = this.generateId(autocmd)
let { nvim } = this
this.autocmds.set(id, autocmd)
this.nvim.command(createCommand(id, autocmd), true)
nvim.pauseNotification()
let cmd = createCommand(id, autocmd)
nvim.command('augroup ' + id, true)
nvim.command(`autocmd!`, true)
nvim.command(cmd, true)
nvim.command('augroup END', true)
nvim.resumeNotification(false, true)
return Disposable.create(() => {
this.autocmds.delete(id)
this.resetDynamicAutocmd()
nvim.command(`autocmd! ${id}`, true)
})
}

public resetDynamicAutocmd(): void {
let { nvim } = this
nvim.pauseNotification()
nvim.command(`autocmd! ${groupName}`, true)
for (let [id, autocmd] of this.autocmds.entries()) {
nvim.command(`autocmd! ${id}`, true)
nvim.command(createCommand(id, autocmd), true)
}
nvim.resumeNotification(false, true)
}

public dispose(): void {
this.nvim.command(`autocmd! ${groupName}`, true)
disposeAll(this.disposables)
this.autocmds.clear()
}
}

export function createCommand(id: number, autocmd: Autocmd): string {
export function createCommand(id: string, autocmd: Autocmd): string {
let args = isFalsyOrEmpty(autocmd.arglist) ? '' : ', ' + autocmd.arglist.join(', ')
let event = Array.isArray(autocmd.event) ? autocmd.event.join(',') : autocmd.event
let pattern = autocmd.pattern != null ? autocmd.pattern : '*'
if (/\buser\b/i.test(event)) {
pattern = ''
}
let method = autocmd.request ? 'request' : 'notify'
return `autocmd ${groupName} ${event} ${pattern} call coc#rpc#${method}('doAutocmd', [${id}${args}])`
return `autocmd ${id} ${event} ${pattern} call coc#rpc#${method}('doAutocmd', ['${id}'${args}])`
}
2 changes: 1 addition & 1 deletion src/handler/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export default class WorkspaceHandler {
})
}

public async doAutocmd(id: number, args: any[]): Promise<void> {
public async doAutocmd(id: string, args: any[]): Promise<void> {
await workspace.autocmds.doAutocmd(id, args)
}

Expand Down
2 changes: 1 addition & 1 deletion src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default class Plugin {
this.addAction('addWorkspaceFolder', (folder: string) => this.handler.workspace.addWorkspaceFolder(folder))
this.addAction('removeWorkspaceFolder', (folder: string) => this.handler.workspace.removeWorkspaceFolder(folder))
this.addAction('getConfig', (key: string) => this.handler.workspace.getConfiguration(key))
this.addAction('doAutocmd', (id: number, ...args: []) => this.handler.workspace.doAutocmd(id, args))
this.addAction('doAutocmd', (id: string, ...args: []) => this.handler.workspace.doAutocmd(id, args))
this.addAction('openLog', () => this.handler.workspace.openLog())
this.addAction('attach', () => workspace.attach())
this.addAction('detach', () => workspace.detach())
Expand Down

0 comments on commit e7abca2

Please sign in to comment.