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

217 lines
7.7 KiB
TypeScript

'use strict'
import { Neovim } from '@chemzqm/neovim'
import fastDiff from 'fast-diff'
import path from 'path'
import { Disposable } from 'vscode-languageserver-protocol'
import { ChangeAnnotation, CreateFile, DeleteFile, Position, RenameFile, TextDocumentEdit, WorkspaceEdit } from 'vscode-languageserver-types'
import { URI } from 'vscode-uri'
import type Keymaps from '../core/keymaps'
import events from '../events'
import { DocumentChange, LinesChange } from '../types'
import { disposeAll } from '../util'
import { isParentFolder } from '../util/fs'
import { getAnnotationKey, getPositionFromEdits, mergeSort } from '../util/textedit'
import Highlighter from './highligher'
const logger = require('../util/logger')('mdoe-editInspect')
export type RecoverFunc = () => Promise<any>
export interface EditState {
edit: WorkspaceEdit
changes: {
[uri: string]: LinesChange
}
recovers: RecoverFunc[]
applied: boolean
}
export interface ChangedFileItem {
index: number
filepath: string
lnum?: number
}
let global_id = 0
export default class EditInspect {
private disposables: Disposable[] = []
private bufnr: number
private items: ChangedFileItem[] = []
private renameMap: Map<string, string> = new Map()
constructor(private nvim: Neovim, private keymaps: Keymaps) {
events.on('BufUnload', bufnr => {
if (bufnr == this.bufnr) this.dispose()
}, null, this.disposables)
}
private addFile(filepath: string, highligher: Highlighter, lnum?: number): void {
this.items.push({
index: highligher.length,
filepath,
lnum
})
}
public async show(state: EditState): Promise<void> {
let { nvim } = this
let id = global_id++
nvim.pauseNotification()
nvim.command(`tabe +setl\\ buftype=nofile CocWorkspaceEdit${id}`, true)
nvim.command(`setl bufhidden=wipe nolist`, true)
nvim.command('setl nobuflisted wrap undolevels=-1 filetype=cocedits noswapfile', true)
await nvim.resumeNotification(true)
let buffer = await nvim.buffer
let cwd = await nvim.call('getcwd')
this.bufnr = buffer.id
const relpath = (uri: string): string => {
let fsPath = URI.parse(uri).fsPath
return isParentFolder(cwd, fsPath, true) ? path.relative(cwd, fsPath) : fsPath
}
const absPath = filepath => {
return path.isAbsolute(filepath) ? filepath : path.join(cwd, filepath)
}
let highligher = new Highlighter()
let map = grouByAnnotation(state.edit.documentChanges ?? [], state.edit.changeAnnotations ?? {})
for (let [label, changes] of map.entries()) {
if (label) {
highligher.addLine(label, 'MoreMsg')
highligher.addLine('')
}
for (let change of changes) {
if (TextDocumentEdit.is(change)) {
let linesChange = state.changes[change.textDocument.uri]
let fsPath = relpath(change.textDocument.uri)
highligher.addTexts([
{ text: 'Change', hlGroup: 'Title' },
{ text: ' ' },
{ text: fsPath, hlGroup: 'Directory' },
{ text: `:${linesChange.lnum}`, hlGroup: 'LineNr' },
])
this.addFile(fsPath, highligher, linesChange.lnum)
highligher.addLine('')
this.addChangedLines(highligher, linesChange, fsPath, linesChange.lnum)
highligher.addLine('')
} else if (CreateFile.is(change) || DeleteFile.is(change)) {
let title = DeleteFile.is(change) ? 'Delete' : 'Create'
let fsPath = relpath(change.uri)
highligher.addTexts([
{ text: title, hlGroup: 'Title' },
{ text: ' ' },
{ text: fsPath, hlGroup: 'Directory' }
])
this.addFile(fsPath, highligher)
highligher.addLine('')
} else if (RenameFile.is(change)) {
let oldPath = relpath(change.oldUri)
let newPath = relpath(change.newUri)
highligher.addTexts([
{ text: 'Rename', hlGroup: 'Title' },
{ text: ' ' },
{ text: oldPath, hlGroup: 'Directory' },
{ text: '->', hlGroup: 'Comment' },
{ text: newPath, hlGroup: 'Directory' }
])
this.renameMap.set(oldPath, newPath)
this.addFile(newPath, highligher)
highligher.addLine('')
}
}
}
nvim.pauseNotification()
highligher.render(buffer)
buffer.setOption('modifiable', false, true)
await nvim.resumeNotification(true)
this.disposables.push(this.keymaps.registerLocalKeymap('n', '<CR>', async () => {
let lnum = await nvim.call('line', '.')
let col = await nvim.call('col', '.')
let find: ChangedFileItem
for (let i = this.items.length - 1; i >= 0; i--) {
let item = this.items[i]
if (lnum >= item.index) {
find = item
break
}
}
if (!find) return
let uri = URI.file(absPath(find.filepath)).toString()
let filepath = this.renameMap.has(find.filepath) ? this.renameMap.get(find.filepath) : find.filepath
await nvim.call('coc#util#open_file', ['tab drop', absPath(filepath)])
// need change old lnum to new lnum
if (typeof find.lnum === 'number') {
let changes = state.edit.documentChanges ?? []
let change = changes.find(o => TextDocumentEdit.is(o) && o.textDocument.uri == uri) as TextDocumentEdit
let lnum = find.lnum
if (change) {
let edits = mergeSort(change.edits, (a, b) => {
let diff = a.range.start.line - b.range.start.line
if (diff === 0) {
return a.range.start.character - b.range.start.character
}
return diff
})
let pos = getPositionFromEdits(Position.create(lnum - 1, 0), edits)
lnum = pos.line + 1
}
await nvim.call('cursor', [lnum, col])
}
nvim.redrawVim()
}, true))
this.disposables.push(this.keymaps.registerLocalKeymap('n', '<esc>', async () => {
nvim.command('bwipeout!', true)
}, true))
}
public addChangedLines(highligher: Highlighter, linesChange: LinesChange, fsPath: string, lnum: number): void {
let diffs = fastDiff(linesChange.oldLines.join('\n'), linesChange.newLines.join('\n'))
for (let i = 0; i < diffs.length; i++) {
let diff = diffs[i]
if (diff[0] == fastDiff.EQUAL) {
let text = diff[1]
if (!text.includes('\n')) {
highligher.addText(text)
} else {
let parts = text.split('\n')
highligher.addText(parts[0])
let curr = lnum + parts.length - 1
highligher.addLine('')
highligher.addTexts([
{ text: 'Change', hlGroup: 'Title' },
{ text: ' ' },
{ text: fsPath, hlGroup: 'Directory' },
{ text: `:${curr}`, hlGroup: 'LineNr' },
])
this.addFile(fsPath, highligher, curr)
highligher.addLine('')
let last = parts[parts.length - 1]
if (last.length > 0) highligher.addText(last)
}
lnum += text.split('\n').length - 1
} else if (diff[0] == fastDiff.DELETE) {
lnum += diff[1].split('\n').length - 1
highligher.addText(diff[1], 'DiffDelete')
} else if (diff[0] == fastDiff.INSERT) {
highligher.addText(diff[1], 'DiffAdd')
}
}
}
public dispose(): void {
disposeAll(this.disposables)
}
}
export function grouByAnnotation(changes: DocumentChange[], annotations: { [id: string]: ChangeAnnotation }): Map<string | null, DocumentChange[]> {
let map: Map<string | null, DocumentChange[]> = new Map()
for (let change of changes) {
let id = getAnnotationKey(change) ?? null
let key = id ? annotations[id].label ?? null : null
let arr = map.get(key)
if (arr) {
arr.push(change)
} else {
map.set(key, [change])
}
}
return map
}