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

243 lines
7.1 KiB
TypeScript

'use strict'
import { Buffer, Neovim, Window } from '@chemzqm/neovim'
import debounce from 'debounce'
import { Disposable } from 'vscode-languageserver-protocol'
import events, { BufEvents } from '../events'
import { parseDocuments } from '../markdown'
import { Documentation, FloatConfig } from '../types'
import { disposeAll } from '../util'
import { Mutex } from '../util/mutex'
import { equals } from '../util/object'
const isVim = process.env.VIM_NODE_RPC == '1'
const logger = require('../util/logger')('model-float')
export interface WindowConfig {
width: number
height: number
col: number
row: number
relative: 'cursor' | 'win' | 'editor'
style?: string
cursorline?: number
title?: string
border?: number[]
autohide?: number
close?: number
}
export interface FloatWinConfig {
maxHeight?: number
maxWidth?: number
preferTop?: boolean
autoHide?: boolean
offsetX?: number
title?: string
border?: number[]
rounded?: boolean
cursorline?: boolean
close?: boolean
highlight?: string
borderhighlight?: string
modes?: string[]
shadow?: boolean
winblend?: number
focusable?: boolean
excludeImages?: boolean
}
/**
* Float window/popup factory for create float/popup around current cursor.
*/
export default class FloatFactory implements Disposable {
private winid = 0
private _bufnr = 0
private closeTs: number
private targetBufnr: number
private mutex: Mutex = new Mutex()
private disposables: Disposable[] = []
private cursor: [number, number]
private onCursorMoved: Function & { clear(): void }
constructor(private nvim: Neovim) {
this.onCursorMoved = debounce(this._onCursorMoved.bind(this), 100)
}
private bindEvents(autoHide: boolean, alignTop: boolean): void {
let eventNames: BufEvents[] = ['InsertLeave', 'InsertEnter', 'BufEnter']
for (let ev of eventNames) {
events.on(ev, bufnr => {
if (bufnr == this._bufnr) return
this.close()
}, null, this.disposables)
}
events.on('MenuPopupChanged', () => {
// avoid intersect with pum
if (events.pumAlignTop == alignTop) {
this.close()
}
}, null, this.disposables)
this.disposables.push(Disposable.create(() => {
this.onCursorMoved.clear()
}))
events.on('CursorMoved', this.onCursorMoved.bind(this, autoHide), this, this.disposables)
events.on('CursorMovedI', this.onCursorMoved.bind(this, autoHide), this, this.disposables)
}
public unbind(): void {
if (this.disposables.length) {
disposeAll(this.disposables)
this.disposables = []
}
}
public _onCursorMoved(autoHide: boolean, bufnr: number, cursor: [number, number]): void {
if (bufnr == this._bufnr) return
if (bufnr == this.targetBufnr && equals(cursor, this.cursor)) {
// cursor not moved
return
}
if (autoHide || bufnr != this.targetBufnr || !events.insertMode) {
this.close()
return
}
}
/**
* Create float window/popup at cursor position.
*
* @deprecated use show method instead
*/
public async create(docs: Documentation[], _allowSelection = false, offsetX = 0): Promise<void> {
await this.show(docs, {
offsetX
})
}
public applyFloatConfig(conf: FloatWinConfig, opts: FloatConfig): FloatWinConfig {
for (let key of Object.keys(opts)) {
if (key == 'border') {
if (opts.border) {
conf.border = [1, 1, 1, 1]
}
continue
}
conf[key] = opts[key]
}
return conf
}
/**
* Show documentations in float window/popup around cursor.
* Window and buffer are reused when possible.
* Window is closed automatically on change buffer, InsertEnter, CursorMoved and CursorMovedI.
*
* @param docs List of documentations.
* @param config Configuration for floating window/popup.
*/
public async show(docs: Documentation[], config: FloatWinConfig = {}): Promise<void> {
if (docs.length == 0 || docs.every(doc => doc.content.length == 0)) {
this.close()
return
}
let curr = Date.now()
let release = await this.mutex.acquire()
try {
await this.createPopup(docs, config, curr)
release()
} catch (e) {
this.nvim.echoError(e)
release()
}
}
private async createPopup(docs: Documentation[], opts: FloatWinConfig, timestamp: number): Promise<void> {
docs = docs.filter(o => o.content.trim().length > 0)
let { lines, codes, highlights } = parseDocuments(docs)
let config: any = {
codes,
highlights,
pumAlignTop: events.pumAlignTop,
preferTop: typeof opts.preferTop === 'boolean' ? opts.preferTop : false,
offsetX: opts.offsetX || 0,
title: opts.title || '',
close: opts.close ? 1 : 0,
rounded: opts.rounded ? 1 : 0,
modes: opts.modes || ['n', 'i', 'ic', 's']
}
if (!isVim) {
if (typeof opts.winblend === 'number') config.winblend = opts.winblend
if (opts.focusable != null) config.focusable = opts.focusable ? 1 : 0
if (opts.shadow) config.shadow = 1
}
if (opts.maxHeight) config.maxHeight = opts.maxHeight
if (opts.maxWidth) config.maxWidth = opts.maxWidth
if (opts.border && !opts.border.every(o => o == 0)) {
config.border = opts.border
}
if (opts.title && !config.border) config.border = [1, 1, 1, 1]
if (opts.highlight) config.highlight = opts.highlight
if (opts.borderhighlight) config.borderhighlight = [opts.borderhighlight]
if (opts.cursorline) config.cursorline = 1
let autoHide = opts.autoHide == false ? false : true
if (autoHide) config.autohide = 1
this.unbind()
let arr = await this.nvim.call('coc#dialog#create_cursor_float', [this.winid, this._bufnr, lines, config])
this.nvim.redrawVim()
if (!arr || arr.length == 0 || this.closeTs > timestamp) {
let winid = arr && arr.length > 0 ? arr[2] : this.winid
if (winid) {
this.winid = 0
this.nvim.call('coc#float#close', [winid], true)
this.nvim.redrawVim()
}
return
}
let [targetBufnr, cursor, winid, bufnr, alignTop] = arr as [number, [number, number], number, number, number]
this.winid = winid
this._bufnr = bufnr
this.targetBufnr = targetBufnr
this.cursor = cursor
this.bindEvents(autoHide, alignTop == 1)
}
/**
* Close float window
*/
public close(): void {
let { winid, nvim } = this
this.closeTs = Date.now()
this.unbind()
if (winid) {
this.winid = 0
nvim.call('coc#float#close', [winid], true)
nvim.redrawVim()
}
}
public checkRetrigger(bufnr: number): boolean {
if (this.winid && this.targetBufnr == bufnr) return true
return false
}
public get bufnr(): number {
return this._bufnr
}
public get buffer(): Buffer | null {
return this.bufnr ? this.nvim.createBuffer(this.bufnr) : null
}
public get window(): Window | null {
return this.winid ? this.nvim.createWindow(this.winid) : null
}
public async activated(): Promise<boolean> {
if (!this.winid) return false
return await this.nvim.call('coc#float#valid', [this.winid]) != 0
}
public dispose(): void {
this.cursor = undefined
this.close()
}
}