lean4-code-actions
Version:
Refactorings and snippets for Lean 4
95 lines (85 loc) • 3.94 kB
text/typescript
import { sep } from 'path'
import { identity } from 'remeda'
import { withWorkspaceEdit } from 'src/utils/WorkspaceEdit'
import { getUriFromLeanNames } from 'src/utils/WorkspaceFolder'
import { ensureWorkspaceFolder } from 'src/utils/workspace'
import { commands } from 'vscode'
import { toString } from '../models/Lean/HieroName'
import { ensureNames, toNamespace } from '../utils/Lean'
import { cloneRegExp } from '../utils/RegExp'
import { getCurrentCodeBlockAt } from '../utils/TextDocument'
import { ensureEditor } from '../utils/TextEditor'
import { Line, Segment, combineFileContent } from '../utils/text'
const getMatcher = (r: RegExp) => (text: string) => text.match(r)
const getRegExpForLineStartingWith = (start: string) => new RegExp('^' + start + '.*', 'gm')
const importRegExp = getRegExpForLineStartingWith('import')
const openRegExp = getRegExpForLineStartingWith('open')
const getImports = getMatcher(importRegExp)
const getOpens = getMatcher(openRegExp)
export async function extractDefinitionToSeparateFile() {
const editor = ensureEditor()
const { document } = editor
const { getText, positionAt } = document
const workspaceFolder = ensureWorkspaceFolder(document.uri)
const textAll = getText()
const allImports = getImports(textAll) || []
const allOpens = getOpens(textAll) || []
const block = getCurrentCodeBlockAt(editor.selection.active, document)
const blockText = getText(block)
const currentNames = ensureNames(document.uri).slice(0, -1)
const selectionNames = getSelectionNames(blockText)
const fullNames = currentNames.concat(selectionNames)
const uri = getUriFromLeanNames(workspaceFolder, fullNames)
// const relativeFilePath = getRelativeFilePathFromNames(fullNames)
// const absoluteFilePath = getAbsoluteFilePathFromRelativeFilePath(document.uri, relativeFilePath)
// if (absoluteFilePath === document.uri.fsPath) throw new Error('The definition belongs to this file. See the extension docs for more details."')
// Create a new file
const content = getContent(allImports, allOpens)(blockText)(currentNames, selectionNames)
const closestImportLastIndex = getLastIndexMatchBefore(importRegExp, document.offsetAt(editor.selection.active), textAll)
const importInsertOffset = closestImportLastIndex ? closestImportLastIndex + 1 : 0
const importInsertSuffix = importInsertOffset === 0 ? '\n' : ''
await withWorkspaceEdit(async edit => {
edit.createFile(uri, { contents: Buffer.from(content) })
edit.delete(document.uri, block)
edit.insert(document.uri, positionAt(importInsertOffset), `import ${toString(fullNames)}\n` + importInsertSuffix)
})
await commands.executeCommand('vscode.open', uri)
}
function getRelativeFilePathFromNames(names: string[]) {
return names.join(sep) + '.lean'
}
const getSelectionNames = (selection: string) => {
// TODO: Rewrite this hack
const namespacesRaw = getSecondStringWithoutSpaces(selection)
if (!namespacesRaw) throw new Error('Selection is invalid: it does not contain any Lean names')
return namespacesRaw.split('.')
}
const getContent = (allImports: Line[], allOpens: Line[]) => (selection: string) => (globalNames: string[], localNames: string[]) => {
// const filePath = uri.toString();
const segments: Segment[] = []
segments.push(allImports)
segments.push(allOpens)
segments.push([toNamespace(globalNames)])
segments.push([selection.trim()])
segments.push([toNamespace(localNames)])
return combineFileContent(segments)
}
function getSecondStringWithoutSpaces(selection: string) {
return selection
.split(' ')
.map(s => s.trim())
.filter(identity)
.at(1)
}
function getLastIndexMatchBefore(regexp: RegExp, index: number, text: string) {
const r = cloneRegExp(regexp)
let prevIndex: number | undefined = undefined
while (r.exec(text)) {
if (r.lastIndex > index) {
break
} else {
prevIndex = r.lastIndex
}
}
return prevIndex
}