fish-lsp
Version:
LSP implementation for fish/fish-shell
434 lines (433 loc) • 15.7 kB
JavaScript
"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;