fish-lsp
Version:
LSP implementation for fish/fish-shell
118 lines (102 loc) • 3.39 kB
text/typescript
import { Command, Position, Range, TextEdit } from 'vscode-languageserver';
import { FishCommandCompletionItem, FishCompletionData } from './types';
import { StaticItems } from './static-items';
import { SyntaxNode } from 'web-tree-sitter';
import { DIAGNOSTIC_COMMENT_REGEX, DiagnosticAction, isValidErrorCode } from '../../diagnostics/comments-handler';
import { FishCompletionList } from './list';
import { SetupData } from './pager';
import { ErrorCodes } from '../../diagnostics/errorCodes';
export function buildCommentCompletions(
line: string,
position: Position,
node: SyntaxNode,
data: SetupData,
word: string,
) {
// FishCompletionItem.createData(data.uri, line, ,detail, documentation)
const hashIndex = line.indexOf('#');
// Create range from the # character to cursor
const range = Range.create(
Position.create(position.line, hashIndex),
position,
);
// Command to retrigger completion
const retriggerCommand: Command = {
title: 'Suggest',
command: 'editor.action.triggerSuggest',
};
const completions: FishCommandCompletionItem[] = [];
if (position.line === 0) {
completions.push(
...StaticItems.shebang.map(item => {
item.textEdit = TextEdit.replace(range, item.label);
return item;
}),
);
}
/**
* add diagnostic comment strings:
* `# @fish-lsp-disable`
*/
const diagnosticComment = getCommentDiagnostics(line, position.line);
if (!diagnosticComment) {
completions.push(
...StaticItems.comment.map((item) => {
item.textEdit = TextEdit.replace(range, `${item.label} `);
item.command = retriggerCommand;
return item;
}));
}
/**
* add diagnostic codes to the completion list
* `# @fish-lsp-disable 1001`
*/
if (diagnosticComment) {
if (diagnosticComment?.codes) {
const codeStrings = diagnosticComment?.codes.map(code => code.toString());
completions.push(
...StaticItems.diagnostic
.filter(item => !codeStrings.includes(item.label))
.map((item) => {
item.command = retriggerCommand;
item.insertText = `${item.label} `;
return item;
}),
);
}
}
const completionData: FishCompletionData = {
word,
position,
uri: data.uri,
line,
};
return FishCompletionList.create(false, completionData, completions);
}
function getCommentDiagnostics(line: string, lineNumber: number) {
const match = line.trim().match(DIAGNOSTIC_COMMENT_REGEX);
if (!match) return null;
const [, action, nextLine, codesStr] = match;
const codeStrings = codesStr ? codesStr.trim().split(/\s+/) : [];
// Parse the diagnostic codes if present
const parsedCodes = codeStrings
.map(codeStr => parseInt(codeStr, 10))
.filter(code => !isNaN(code));
const validCodes: ErrorCodes.codeTypes[] = [];
const invalidCodes: string[] = [];
codeStrings.forEach((codeStr, idx) => {
const code = parsedCodes[idx];
if (code && !isNaN(code) && isValidErrorCode(code)) {
validCodes.push(code as ErrorCodes.codeTypes);
} else {
invalidCodes.push(codeStr);
}
});
return {
action: action as DiagnosticAction,
target: nextLine ? 'next-line' : 'line',
codes: validCodes,
lineNumber: lineNumber,
invalidCodes: invalidCodes.length > 0 ? invalidCodes : undefined,
};
}