UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

484 lines (483 loc) 17.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.documents = exports.LspDocuments = exports.LspDocument = void 0; const fs_1 = require("fs"); const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument"); const vscode_languageserver_1 = require("vscode-languageserver"); const path = __importStar(require("path")); const vscode_uri_1 = require("vscode-uri"); const os_1 = require("os"); const translation_1 = require("./utils/translation"); const workspace_manager_1 = require("./utils/workspace-manager"); const file_operations_1 = require("./utils/file-operations"); const logger_1 = require("./logger"); const Locations = __importStar(require("./utils/locations")); const symbol_1 = require("./parsing/symbol"); class LspDocument { document; constructor(doc) { const { uri, languageId, version, text } = doc; this.document = vscode_languageserver_textdocument_1.TextDocument.create(uri, languageId, version, text); } static createTextDocumentItem(uri, text) { return new LspDocument({ uri, languageId: 'fish', version: 1, text, }); } static fromTextDocument(doc) { const item = vscode_languageserver_1.TextDocumentItem.create(doc.uri, doc.languageId, doc.version, doc.getText()); return new LspDocument(item); } static createFromUri(uri) { const content = file_operations_1.SyncFileHelper.read((0, translation_1.uriToPath)(uri)); return LspDocument.createTextDocumentItem(uri, content); } static createFromPath(path) { const content = file_operations_1.SyncFileHelper.read(path); return LspDocument.createTextDocumentItem((0, translation_1.pathToUri)(path), content); } static testUri(uri) { const removeString = 'test-data/workspaces'; if (uri.includes(removeString)) { return 'file:///…/' + uri.slice(uri.indexOf(removeString) + removeString.length + 1); } return uri; } static testUtil(uri) { const shortUri = LspDocument.testUri(uri); const fullPath = (0, translation_1.uriToPath)(uri); const parentDir = path.dirname(fullPath); const relativePath = shortUri.slice(shortUri.indexOf(parentDir) + parentDir.length + 1); return { uri, shortUri, fullPath, relativePath, parentDir, }; } static create(param) { if (typeof param === 'string' && (0, translation_1.isPath)(param)) return LspDocument.createFromPath(param); if (typeof param === 'string' && (0, translation_1.isUri)(param)) return LspDocument.createFromUri(param); if (LspDocument.is(param)) return LspDocument.fromTextDocument(param.document); if ((0, translation_1.isTextDocumentItem)(param)) return LspDocument.createTextDocumentItem(param.uri, param.text); if ((0, translation_1.isTextDocument)(param)) return LspDocument.fromTextDocument(param); logger_1.logger.error('Invalid parameter type `LspDocument.create()`: ', param); return undefined; } static async createFromUriAsync(uri) { const content = await fs_1.promises.readFile((0, translation_1.uriToPath)(uri), 'utf8'); return LspDocument.createTextDocumentItem(uri, content); } asTextDocumentItem() { return { uri: this.document.uri, languageId: this.document.languageId, version: this.document.version, text: this.document.getText(), }; } asTextDocumentIdentifier() { return { uri: this.document.uri, }; } get uri() { return this.document.uri; } get languageId() { return this.document.languageId; } get version() { return this.document.version; } get path() { return (0, translation_1.uriToPath)(this.document.uri); } getText(range) { return this.document.getText(range); } positionAt(offset) { return this.document.positionAt(offset); } offsetAt(position) { return this.document.offsetAt(position); } get lineCount() { return this.document.lineCount; } create(uri, languageId, version, text) { return new LspDocument({ uri, languageId: languageId || 'fish', version: version || 1, text, }); } getLine(line) { if (Locations.Position.is(line)) { line = line.line; } else if (Locations.Range.is(line)) { line = line.start.line; } else if (symbol_1.FishSymbol.is(line)) { line = line.range.start.line; } const lines = this.document.getText().split('\n'); return lines[line] || ''; } getLineBeforeCursor(position) { const lineStart = vscode_languageserver_1.Position.create(position.line, 0); const lineEnd = vscode_languageserver_1.Position.create(position.line, position.character); const lineRange = vscode_languageserver_1.Range.create(lineStart, lineEnd); return this.getText(lineRange); } getLineRange(line) { const lineStart = this.getLineStart(line); const lineEnd = this.getLineEnd(line); return vscode_languageserver_1.Range.create(lineStart, lineEnd); } getLineEnd(line) { const nextLineOffset = this.getLineOffset(line + 1); return this.positionAt(nextLineOffset - 1); } getLineOffset(line) { const lineStart = this.getLineStart(line); return this.offsetAt(lineStart); } getLineStart(line) { return vscode_languageserver_1.Position.create(line, 0); } getIndentAtLine(line) { const lineText = this.getLine(line); const indent = lineText.match(/^\s+/); return indent ? indent[0] : ''; } update(changes) { this.document = vscode_languageserver_textdocument_1.TextDocument.update(this.document, changes, this.version + 1); return LspDocument.fromTextDocument(this.document); } asVersionedIdentifier() { return vscode_languageserver_1.VersionedTextDocumentIdentifier.create(this.uri, this.version); } rename(newUri) { this.document = vscode_languageserver_textdocument_1.TextDocument.create(newUri, this.languageId, this.version, this.getText()); } getFilePath() { return (0, translation_1.uriToPath)(this.uri); } getFilename() { return this.uri.split('/').pop(); } getRelativeFilenameToWorkspace() { const home = (0, os_1.homedir)(); const path = this.uri.replace(home, '~'); const dirs = path.split('/'); const workspaceRootIndex = dirs.find(dir => dir === 'fish') ? dirs.indexOf('fish') : dirs.find(dir => ['conf.d', 'functions', 'completions', 'config.fish'].includes(dir)) ? dirs.findIndex(dir => ['conf.d', 'functions', 'completions', 'config.fish'].includes(dir)) : dirs.length - 1; return dirs.slice(workspaceRootIndex).join('/'); } isFunction() { const pathArray = this.uri.split('/'); const fileName = pathArray.pop(); const parentDir = pathArray.pop(); if (parentDir === 'conf.d' || fileName === 'config.fish') { return true; } return parentDir === 'functions'; } isAutoloadedFunction() { return this.getAutoloadType() === 'functions'; } isAutoloadedCompletion() { return this.getAutoloadType() === 'completions'; } isAutoloadedConfd() { return this.getAutoloadType() === 'conf.d'; } shouldAnalyzeInBackground() { const pathArray = this.uri.split('/'); const fileName = pathArray.pop(); const parentDir = pathArray.pop(); return parentDir && ['functions', 'conf.d', 'completions'].includes(parentDir?.toString()) || fileName === 'config.fish'; } getWorkspace() { return workspace_manager_1.workspaceManager.findContainingWorkspace(this.uri) || undefined; } getFolderType() { const docPath = (0, translation_1.uriToPath)(this.uri); if (!docPath) return null; const dirName = path.basename(path.dirname(docPath)); const fileName = path.basename(docPath); if (dirName === 'functions') return 'functions'; if (dirName === 'conf.d') return 'conf.d'; if (dirName === 'completions') return 'completions'; if (fileName === 'config.fish') return 'config'; return ''; } isAutoloaded() { const folderType = this.getFolderType(); if (!folderType) return false; return ['functions', 'conf.d', 'config'].includes(folderType); } isAutoloadedUri() { const folderType = this.getFolderType(); if (!folderType) return false; return ['functions', 'conf.d', 'config', 'completions'].includes(folderType); } isAutoloadedWithPotentialCompletions() { const folderType = this.getFolderType(); if (!folderType) return false; return ['conf.d', 'config', 'completions'].includes(folderType); } getAutoloadType() { return this.getFolderType() || ''; } getAutoLoadName() { if (!this.isAutoloadedUri()) { return ''; } const parts = (0, translation_1.uriToPath)(this.uri)?.split('/') || []; const name = parts[parts.length - 1]; return name.replace('.fish', ''); } getFileName() { const items = (0, translation_1.uriToPath)(this.uri).split('/') || []; const name = items.length > 0 ? items.pop() : (0, translation_1.uriToPath)(this.uri); return name; } getLines() { const lines = this.getText().split('\n'); return lines.length; } static is(value) { return (typeof value === 'object' && value !== null && typeof value.asTextDocumentItem === 'function' && typeof value.asTextDocumentIdentifier === 'function' && typeof value.getAutoloadType === 'function' && typeof value.isAutoloaded === 'function' && typeof value.path === 'string' && typeof value.getFileName === 'function' && typeof value.getRelativeFilenameToWorkspace === 'function' && typeof value.getLine === 'function' && typeof value.getLines === 'function' && typeof value.uri === 'string' && typeof value.getText === 'function'); } } exports.LspDocument = LspDocument; class LspDocuments { _files = []; documents = new Map(); static create() { return new LspDocuments(); } static from(documents) { const newDocuments = new LspDocuments(); newDocuments.documents.clear(); newDocuments._files.length = 0; documents.documents.forEach((doc, file) => { newDocuments.documents.set(file, doc); newDocuments._files.push(file); }); return newDocuments; } copy(documents) { this.documents.clear(); this._files.push(...documents._files); documents.documents.forEach((doc, file) => { this.documents.set(file, doc); }); return this; } get openDocuments() { const result = []; for (const file of this._files.toReversed()) { const document = this.documents.get(file); if (document) { result.push(document); } } return result; } get files() { return this._files; } get(file) { if (!file) { return undefined; } const document = this.documents.get(file); if (!document) { return undefined; } if (this.files[0] !== file) { this._files.splice(this._files.indexOf(file), 1); this._files.unshift(file); } return document; } openPath(path, doc) { const lspDocument = new LspDocument(doc); this.documents.set(path, lspDocument); this._files.unshift(path); return lspDocument; } getPathFromParam(param) { if ((0, translation_1.isUri)(param)) { return (0, translation_1.uriToPath)(param); } if ((0, translation_1.isPath)(param)) { return param; } if ((0, translation_1.isTextDocument)(param)) { return (0, translation_1.uriToPath)(param.uri); } if ((0, translation_1.isTextDocumentItem)(param)) { return (0, translation_1.uriToPath)(param.uri); } if (LspDocument.is(param)) { return param.path; } throw new Error('Invalid parameter type'); } open(param) { const path = this.getPathFromParam(param); if (this.documents.has(path)) { return false; } const newDoc = LspDocument.create(param); this.documents.set(path, newDoc); this._files.unshift(path); return true; } isOpen(path) { if (vscode_uri_1.URI.isUri(path)) { path = (0, translation_1.uriToPath)(path); } return this.documents.has(path); } get uris() { return Array.from(this._files).map(file => (0, translation_1.pathToUri)(file)); } getDocument(uri) { const path = (0, translation_1.uriToPath)(uri); return this.documents.get(path); } openTextDocument(document) { const path = (0, translation_1.uriToPath)(document.uri); if (this.documents.has(path)) { return this.documents.get(path); } const lspDocument = LspDocument.fromTextDocument(document); this.documents.set(path, lspDocument); this._files.unshift(path); return lspDocument; } updateTextDocument(textDocument) { const path = (0, translation_1.uriToPath)(textDocument.uri); this.documents.set(path, LspDocument.fromTextDocument(textDocument)); return this.documents.get(path); } applyChanges(uri, changes) { const path = (0, translation_1.uriToPath)(uri); let document = this.documents.get(path); if (document) { document = document.update(changes); this.documents.set(path, document); } } set(document) { const path = (0, translation_1.uriToPath)(document.uri); this.documents.set(path, document); } all() { return Array.from(this.documents.values()); } closeTextDocument(document) { const path = (0, translation_1.uriToPath)(document.uri); return this.close(path); } close(param) { const path = this.getPathFromParam(param); const document = this.documents.get(path); if (!document) { return undefined; } this.documents.delete(path); this._files.splice(this._files.indexOf(path), 1); return document; } closeAll() { this.documents.clear(); this._files.length = 0; } rename(oldFile, newFile) { const document = this.documents.get(oldFile); if (!document) { return false; } document.rename(newFile); this.documents.delete(oldFile); this.documents.set(newFile, document); this._files[this._files.indexOf(oldFile)] = newFile; return true; } toResource(filepath) { const document = this.documents.get(filepath); if (document) { return vscode_uri_1.URI.parse(document.uri); } return vscode_uri_1.URI.file(filepath); } clear() { this.documents.clear(); this._files.length = 0; } } exports.LspDocuments = LspDocuments; exports.documents = LspDocuments.create();