1
0
Fork 0
mirror of synced 2024-06-26 02:31:09 -04:00
ultimate-vim/sources_non_forked/coc.nvim/src/model/picker.ts
2022-07-20 13:20:15 +08:00

242 lines
7.8 KiB
TypeScript

'use strict'
import { Buffer, Neovim } from '@chemzqm/neovim'
import { CancellationToken, Disposable, Emitter, Event } from 'vscode-languageserver-protocol'
import events from '../events'
import { HighlightItem, QuickPickItem } from '../types'
import { disposeAll } from '../util'
import { byteLength } from '../util/string'
import { DialogPreferences } from './dialog'
import Popup from './popup'
const logger = require('../util/logger')('model-dialog')
const isVim = process.env.VIM_NODE_RPC == '1'
interface PickerConfig {
title: string
items: QuickPickItem[]
}
/**
* Pick multiple items from dialog
*/
export default class Picker {
private bufnr: number
private win: Popup | undefined
private picked: Set<number> = new Set()
private total: number
private disposables: Disposable[] = []
private keyMappings: Map<string, (character: string) => void> = new Map()
private readonly _onDidClose = new Emitter<number[] | undefined>()
public readonly onDidClose: Event<number[] | undefined> = this._onDidClose.event
constructor(private nvim: Neovim, private config: PickerConfig, token?: CancellationToken) {
for (let i = 0; i < config.items.length; i++) {
let item = config.items[i]
if (item.picked) this.picked.add(i)
}
this.total = config.items.length
if (token) {
token.onCancellationRequested(() => {
this.win?.close()
})
}
this.disposables.push(this._onDidClose)
this.addKeymappings()
}
public get currIndex(): number {
return this.win ? this.win.currIndex : 0
}
private attachEvents(): void {
events.on('InputChar', this.onInputChar.bind(this), null, this.disposables)
events.on('BufWinLeave', bufnr => {
if (bufnr == this.bufnr) {
this._onDidClose.fire(undefined)
this.bufnr = undefined
this.win = undefined
this.dispose()
}
}, null, this.disposables)
events.on('FloatBtnClick', (bufnr, idx) => {
if (bufnr == this.bufnr) {
if (idx == 0) {
let selected = Array.from(this.picked)
this._onDidClose.fire(selected.length ? selected : undefined)
} else {
this._onDidClose.fire(undefined)
}
this.dispose()
}
}, null, this.disposables)
}
private addKeymappings(): void {
let { nvim } = this
const toggleSelect = idx => {
if (this.picked.has(idx)) {
this.picked.delete(idx)
} else {
this.picked.add(idx)
}
}
this.addKeys('<LeftRelease>', async () => {
// not work on vim
if (isVim || !this.win) return
let [winid, lnum, col] = await nvim.eval('[v:mouse_winid,v:mouse_lnum,v:mouse_col]') as [number, number, number]
// can't simulate vvar.
if (global.hasOwnProperty('__TEST__')) {
let res = await nvim.getVar('mouse_position')
winid = res[0]
lnum = res[1]
col = res[2]
}
nvim.pauseNotification()
if (winid == this.win.winid) {
if (col <= 3) {
toggleSelect(lnum - 1)
this.changeLine(lnum - 1)
} else {
this.setCursor(lnum - 1)
}
}
nvim.call('win_gotoid', [winid], true)
nvim.call('cursor', [lnum, col], true)
nvim.call('coc#float#nvim_float_click', [], true)
nvim.command('redraw', true)
await nvim.resumeNotification()
})
this.addKeys(['<esc>', '<C-c>'], () => {
this._onDidClose.fire(undefined)
this.dispose()
})
this.addKeys('<cr>', () => {
if (this.picked.size == 0) {
this._onDidClose.fire(undefined)
} else {
let selected = Array.from(this.picked)
this._onDidClose.fire(selected)
}
this.dispose()
})
this.addKeys(['j', '<down>', '<tab>', '<C-n>'], () => {
this.win.setCursor(this.currIndex + 1, true)
})
this.addKeys(['k', '<up>', '<s-tab>', '<C-p>'], () => {
this.win.setCursor(this.currIndex - 1, true)
})
this.addKeys(['g'], () => {
this.win.setCursor(0, true)
})
this.addKeys(['G'], () => {
this.win.setCursor(this.total - 1, true)
})
this.addKeys(' ', async () => {
let idx = this.currIndex
toggleSelect(idx)
nvim.pauseNotification()
this.changeLine(idx)
this.setCursor(this.currIndex + 1)
nvim.command('redraw', true)
await nvim.resumeNotification()
})
this.addKeys('<C-f>', async () => {
await this.win?.scrollForward()
})
this.addKeys('<C-b>', async () => {
await this.win?.scrollBackward()
})
}
public async show(preferences: DialogPreferences = {}): Promise<number> {
let { nvim } = this
let { title, items } = this.config
let opts: any = { close: 1, cursorline: 1 }
if (preferences.maxHeight) opts.maxHeight = preferences.maxHeight
if (preferences.maxWidth) opts.maxWidth = preferences.maxWidth
if (title) opts.title = title
if (preferences.floatHighlight) opts.highlight = preferences.floatHighlight
if (preferences.floatBorderHighlight) opts.borderhighlight = [preferences.floatBorderHighlight]
if (preferences.pickerButtons) {
let shortcut = preferences.pickerButtonShortcut
opts.buttons = ['Submit' + (shortcut ? ' <cr>' : ''), 'Cancel' + (shortcut ? ' <esc>' : '')]
}
if (preferences.rounded) opts.rounded = 1
if (preferences.confirmKey && preferences.confirmKey != '<cr>') {
this.addKeys(preferences.confirmKey, () => {
this._onDidClose.fire(undefined)
this.dispose()
})
}
let lines = []
let highlights: HighlightItem[] = []
for (let i = 0; i < items.length; i++) {
let item = items[i]
let line = `[${item.picked ? 'x' : ' '}] ${item.label}`
if (item.description) {
let start = byteLength(line)
line = line + ` ${item.description}`
highlights.push({ hlGroup: 'Comment', lnum: i, colStart: start, colEnd: byteLength(line) })
}
lines.push(line)
}
if (highlights.length) opts.highlights = highlights
let res = await nvim.call('coc#dialog#create_dialog', [lines, opts]) as [number, number]
this.win = new Popup(nvim, res[0], res[1], lines.length)
this.bufnr = res[1]
nvim.call('coc#prompt#start_prompt', ['picker'], true)
this.attachEvents()
this.win.setCursor(0, true)
return res[0]
}
public get buffer(): Buffer {
return this.bufnr ? this.nvim.createBuffer(this.bufnr) : undefined
}
public dispose(): void {
this.picked.clear()
this.keyMappings.clear()
disposeAll(this.disposables)
this.nvim.call('coc#prompt#stop_prompt', ['picker'], true)
this.win?.close()
this.win = undefined
}
private async onInputChar(session: string, character: string): Promise<void> {
if (session != 'picker' || !this.win) return
let fn = this.keyMappings.get(character)
if (fn) {
await Promise.resolve(fn(character))
} else {
logger.warn(`Ignored key press: ${character}`)
}
}
private changeLine(index: number): void {
let { nvim } = this
let item = this.config.items[index]
if (!item) return
let line = `[${this.picked.has(index) ? 'x' : ' '}] ${item.label}`
let col = byteLength(line)
if (item.description) line = line + ` ${item.description}`
nvim.call('setbufline', [this.bufnr, index + 1, line], true)
let buf = nvim.createBuffer(this.bufnr)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
buf.addHighlight({ hlGroup: 'Comment', line: index, srcId: 1, colStart: col, colEnd: -1 })
}
private setCursor(index: number): void {
if (!this.win) return
this.win.setCursor(index)
}
private addKeys(keys: string | string[], fn: (character: string) => void): void {
if (Array.isArray(keys)) {
for (let key of keys) {
this.keyMappings.set(key, fn)
}
} else {
this.keyMappings.set(keys, fn)
}
}
}