UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

434 lines (433 loc) 15.7 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.UriTracker = exports.FishUriWorkspace = exports.Workspace = void 0; exports.getWorkspacePathsFromInitializationParams = getWorkspacePathsFromInitializationParams; exports.getFileUriSet = getFileUriSet; exports.syncGetFileUriSet = syncGetFileUriSet; exports.initializeDefaultFishWorkspaces = initializeDefaultFishWorkspaces; const fastGlob = __importStar(require("fast-glob")); const translation_1 = require("./translation"); const config_1 = require("../config"); const logger_1 = require("../logger"); const path_1 = require("path"); const env_manager_1 = require("./env-manager"); const file_operations_1 = require("./file-operations"); const analyze_1 = require("../analyze"); const workspace_manager_1 = require("./workspace-manager"); function getWorkspacePathsFromInitializationParams(params) { const result = []; const { rootUri, rootPath, workspaceFolders } = params; logger_1.logger.log('getWorkspacePathsFromInitializationParams(params)', { rootUri, rootPath, workspaceFolders }); if (rootUri) { result.push((0, translation_1.uriToPath)(rootUri)); } if (rootPath) { result.push(rootPath); } if (workspaceFolders) { result.push(...workspaceFolders.map(folder => (0, translation_1.uriToPath)(folder.uri))); } return Array.from(new Set(result)); } async function getFileUriSet(path) { const stream = fastGlob.stream('**/*.fish', { cwd: path, absolute: true }); const result = new Set(); for await (const entry of stream) { const absPath = entry.toString(); const uri = (0, translation_1.pathToUri)(absPath); result.add(uri); } return result; } function syncGetFileUriSet(path) { const result = new Set(); const entries = fastGlob.sync('**/*.fish', { cwd: path, absolute: true }); for (const entry of entries) { const absPath = entry.toString(); const uri = (0, translation_1.pathToUri)(absPath); result.add(uri); } return result; } async function initializeDefaultFishWorkspaces(...uris) { const newWorkspaces = uris.map(uri => { return FishUriWorkspace.create(uri); }).filter((ws) => ws !== null); const tmpConfigWorkspaces = FishUriWorkspace.initializeEnvWorkspaces(); const configWorkspaces = tmpConfigWorkspaces.filter(ws => !newWorkspaces.some(newWs => newWs.uri === ws.uri)); const allWorkspaces = [ ...newWorkspaces, ...configWorkspaces, ].filter((workspace, index, self) => index === self.findIndex(w => w.uri === workspace.uri)).map(({ name, uri, path }) => Workspace.create(name, uri, path)); const defaultSpaces = await Promise.all(allWorkspaces); const results = defaultSpaces.filter((ws) => ws !== null); results.forEach((ws, idx) => { logger_1.logger.info(`Initialized workspace '${ws.name}' @ ${idx}`, { name: ws.name, uri: ws.uri, path: ws.path, }); workspace_manager_1.workspaceManager.add(ws); }); return results; } class Workspace { name; uri; path; uris = new UriTracker(); symbols = new Map(); static async create(name, uri, path) { const isDirectory = file_operations_1.SyncFileHelper.isDirectory(path); let foundUris = new Set(); if (isDirectory) { if (!path.startsWith('/tmp')) { foundUris = await getFileUriSet(path); } } else { foundUris = new Set([uri]); } return new Workspace(name, uri, path, foundUris); } static syncCreateFromUri(uri) { const path = (0, translation_1.uriToPath)(uri); try { const isDirectory = file_operations_1.SyncFileHelper.isDirectory(path); const workspace = FishUriWorkspace.create(uri); if (!workspace) return null; let foundUris = new Set(); if (isDirectory || file_operations_1.SyncFileHelper.isDirectory(workspace.path)) { if (!workspace.path.startsWith('/tmp')) { foundUris = syncGetFileUriSet(workspace.path); } } else { foundUris = new Set([workspace.uri]); } return new Workspace(workspace.name, workspace.uri, workspace.path, foundUris); } catch (e) { logger_1.logger.error('syncCreateFromUri', { uri, error: e }); return null; } } constructor(name, uri, path, fileUris) { this.name = name; this.uri = uri; this.path = path; this.uris = UriTracker.create(...Array.from(fileUris)); } get allUris() { return this.uris.allAsSet(); } contains(...checkUris) { for (const uri of checkUris) { if (!this.uris.has(uri)) { return false; } } return true; } shouldContain(uri) { return uri.startsWith(this.uri) && !this.uris.allAsSet().has(uri); } addUri(uri) { this.uris.add(uri); } add(...newUris) { this.uris.add(...newUris); } addPending(...newUris) { this.uris.addPending(newUris); } findMatchingFishIdentifiers(fishIdentifier) { const matches = []; const toMatch = `/${fishIdentifier}.fish`; for (const uri of Array.from(this.uris.allAsSet())) { if (uri.endsWith(toMatch)) { matches.push(uri); } } return matches; } findDocument(callbackfn) { for (const uri of this.uris.all) { const doc = analyze_1.analyzer.getDocument(uri); if (doc && callbackfn(doc)) { return doc; } } return undefined; } isMutable() { return config_1.config.fish_lsp_modifiable_paths.includes(this.path) || file_operations_1.SyncFileHelper.isWriteable(this.path); } isLoadable() { return config_1.config.fish_lsp_all_indexed_paths.includes(this.path); } isAnalyzed() { return this.uris.pendingCount === 0 && this.allUris.size > 0; } hasCompletionUri(fishIdentifier) { const matchingUris = this.findMatchingFishIdentifiers(fishIdentifier); return matchingUris.some(uri => uri.endsWith(`/completions/${fishIdentifier}.fish`)); } hasFunctionUri(fishIdentifier) { const matchingUris = this.findMatchingFishIdentifiers(fishIdentifier); return matchingUris.some(uri => uri.endsWith(`/functions/${fishIdentifier}.fish`)); } hasCompletionAndFunction(fishIdentifier) { return this.hasFunctionUri(fishIdentifier) && this.hasCompletionUri(fishIdentifier); } getCompletionUri(fishIdentifier) { const matchingUris = this.findMatchingFishIdentifiers(fishIdentifier); return matchingUris.find(uri => uri.endsWith(`/completions/${fishIdentifier}.fish`)); } pendingDocuments() { const docs = []; for (const uri of this.uris.pending) { const path = (0, translation_1.uriToPath)(uri); const doc = file_operations_1.SyncFileHelper.loadDocumentSync(path); if (!doc) { logger_1.logger.error('pendingDocuments', { uri, path }); continue; } docs.push(doc); } return docs; } allDocuments() { const docs = []; for (const uri of this.uris.all) { const analyzedDoc = analyze_1.analyzer.getDocument(uri); if (analyzedDoc) { docs.push(analyzedDoc); continue; } const path = (0, translation_1.uriToPath)(uri); const doc = file_operations_1.SyncFileHelper.loadDocumentSync(path); if (!doc) { logger_1.logger.error('allDocuments', { uri, path }); continue; } docs.push(doc); } return docs; } get paths() { return Array.from(this.allUris).map(uri => (0, translation_1.uriToPath)(uri)); } getUris() { return Array.from(this.allUris || []); } equals(other) { if (!other) return false; return this.name === other.name && this.uri === other.uri && this.path === other.path; } needsAnalysis() { return this.uris.pendingCount > 0; } setAllPending() { for (const uri of this.uris.all) { this.uris.markPending(uri); } } } exports.Workspace = Workspace; var FishUriWorkspace; (function (FishUriWorkspace) { const FISH_DIRS = ['functions', 'completions', 'conf.d']; const CONFIG_FILE = 'config.fish'; function isTmpWorkspace(uri) { const path = (0, translation_1.uriToPath)(uri); return path.startsWith('/tmp'); } FishUriWorkspace.isTmpWorkspace = isTmpWorkspace; function trimFishFilePath(uri) { const path = (0, translation_1.uriToPath)(uri); if (!path) return undefined; const base = (0, path_1.basename)(path); if (base === CONFIG_FILE || path.startsWith('/tmp')) return path; return !file_operations_1.SyncFileHelper.isDirectory(path) && base.endsWith('.fish') ? (0, path_1.dirname)(path) : path; } FishUriWorkspace.trimFishFilePath = trimFishFilePath; function getWorkspaceRootFromUri(uri) { const path = (0, translation_1.uriToPath)(uri); if (!path) return undefined; let current = path; const base = (0, path_1.basename)(current); if (current.startsWith('/tmp')) { return current; } if (file_operations_1.SyncFileHelper.isDirectory(current) && isFishWorkspacePath(current)) { return current; } if (FISH_DIRS.includes(base) || base === CONFIG_FILE) { return (0, path_1.dirname)(current); } while (current !== (0, path_1.dirname)(current)) { for (const dir of FISH_DIRS) { if ((0, path_1.basename)(current) === dir) { return (0, path_1.dirname)(current); } } if (FISH_DIRS.some(dir => isFishWorkspacePath((0, path_1.join)(current, dir))) || isFishWorkspacePath((0, path_1.join)(current, CONFIG_FILE))) { return current; } current = (0, path_1.dirname)(current); } return config_1.config.fish_lsp_all_indexed_paths.find(p => path.startsWith(p)); } FishUriWorkspace.getWorkspaceRootFromUri = getWorkspaceRootFromUri; function getWorkspaceName(uri) { const root = getWorkspaceRootFromUri(uri); if (!root) return ''; const specialName = env_manager_1.env.findAutolaodedKey(root); logger_1.logger.debug('getWorkspaceName', { root, specialName }); if (specialName) return specialName; const base = (0, path_1.basename)(root); if (base === 'fish') return root; return base; } FishUriWorkspace.getWorkspaceName = getWorkspaceName; function isFishWorkspacePath(path) { if (file_operations_1.SyncFileHelper.isDirectory(path) && (file_operations_1.SyncFileHelper.exists(`${path}/functions`) || file_operations_1.SyncFileHelper.exists(`${path}/completions`) || file_operations_1.SyncFileHelper.exists(`${path}/conf.d`))) { return file_operations_1.SyncFileHelper.isDirectory(path); } if ((0, path_1.basename)(path) === CONFIG_FILE) { return true; } return config_1.config.fish_lsp_all_indexed_paths.includes(path); } FishUriWorkspace.isFishWorkspacePath = isFishWorkspacePath; function isInFishWorkspace(uri) { return getWorkspaceRootFromUri(uri) !== undefined; } FishUriWorkspace.isInFishWorkspace = isInFishWorkspace; function initializeEnvWorkspaces() { return config_1.config.fish_lsp_all_indexed_paths .map(path => create((0, translation_1.pathToUri)(path))) .filter((ws) => ws !== null); } FishUriWorkspace.initializeEnvWorkspaces = initializeEnvWorkspaces; function create(uri) { if (isTmpWorkspace(uri)) { return { name: (0, translation_1.uriToPath)(uri), uri, path: (0, translation_1.uriToPath)(uri), }; } if (!isInFishWorkspace(uri)) return null; const trimmedUri = trimFishFilePath(uri); if (!trimmedUri) return null; const rootPath = getWorkspaceRootFromUri(trimmedUri); const workspaceName = getWorkspaceName(trimmedUri); if (!rootPath || !workspaceName) return null; return { name: workspaceName, uri: (0, translation_1.pathToUri)(rootPath), path: rootPath, }; } FishUriWorkspace.create = create; })(FishUriWorkspace || (exports.FishUriWorkspace = FishUriWorkspace = {})); class UriTracker { _indexed = new Set(); _pending = new Set(); static create(...uris) { const tracker = new UriTracker(); for (const uri of uris) { tracker.add(uri); } return tracker; } add(...uris) { for (const uri of uris) { if (!this._indexed.has(uri)) { this._pending.add(uri); } } return this; } addPending(uris) { for (const uri of uris) { if (!this._indexed.has(uri)) { this._pending.add(uri); } } return this; } markIndexed(uri) { this._pending.delete(uri); this._indexed.add(uri); } markPending(uri) { this._indexed.delete(uri); this._pending.add(uri); } get all() { return [...this._indexed, ...this._pending]; } allAsSet() { return new Set([...this._indexed, ...this._pending]); } get indexed() { return Array.from(this._indexed); } get pending() { return Array.from(this._pending); } get pendingCount() { return this._pending.size; } get indexedCount() { return this._indexed.size; } isIndexed(uri) { return this._indexed.has(uri); } has(uri) { return this._indexed.has(uri) || this._pending.has(uri); } } exports.UriTracker = UriTracker;