remove unused packages
This commit is contained in:
parent
2ba4cf4f1e
commit
5742ff6b75
@ -1,2 +0,0 @@
|
|||||||
metaModel.json
|
|
||||||
metaModel.schema.json
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import url from "node:url";
|
|
||||||
|
|
||||||
const __filename = url.fileURLToPath(new URL(import.meta.url));
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
|
|
||||||
const metaModelPath = path.join(__dirname, "metaModel.json");
|
|
||||||
const metaModelSchemaPath = path.join(__dirname, "metaModelSchema.mts");
|
|
||||||
|
|
||||||
const hash = "dadd73f7fc283b4d0adb602adadcf4be16ef3a7b";
|
|
||||||
|
|
||||||
const metaModelURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${hash}/protocol/metaModel.json`;
|
|
||||||
const metaModelSchemaURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${hash}/tools/src/metaModel.ts`;
|
|
||||||
|
|
||||||
const metaModelResponse = await fetch(metaModelURL);
|
|
||||||
let metaModel = await metaModelResponse.text();
|
|
||||||
metaModel = metaModel.replaceAll('"_InitializeParams"', '"InitializeParamsBase"');
|
|
||||||
fs.writeFileSync(metaModelPath, metaModel);
|
|
||||||
|
|
||||||
const metaModelSchemaResponse = await fetch(metaModelSchemaURL);
|
|
||||||
const metaModelSchema = await metaModelSchemaResponse.text();
|
|
||||||
fs.writeFileSync(metaModelSchemaPath, metaModelSchema);
|
|
||||||
@ -1,913 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import cp from "node:child_process";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import url from "node:url";
|
|
||||||
import which from "which";
|
|
||||||
import type {
|
|
||||||
MetaModel,
|
|
||||||
Notification,
|
|
||||||
OrType,
|
|
||||||
Property,
|
|
||||||
Request,
|
|
||||||
Structure,
|
|
||||||
Type,
|
|
||||||
} from "./metaModelSchema.mts";
|
|
||||||
|
|
||||||
const __filename = url.fileURLToPath(new URL(import.meta.url));
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
|
|
||||||
const out = path.resolve(__dirname, "../lsp_generated.go");
|
|
||||||
const metaModelPath = path.resolve(__dirname, "metaModel.json");
|
|
||||||
|
|
||||||
if (!fs.existsSync(metaModelPath)) {
|
|
||||||
console.error("Meta model file not found; did you forget to run fetchModel.mjs?");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const model: MetaModel = JSON.parse(fs.readFileSync(metaModelPath, "utf-8"));
|
|
||||||
|
|
||||||
// Preprocess the model to inline extends/mixins contents
|
|
||||||
function preprocessModel() {
|
|
||||||
const structureMap = new Map<string, Structure>();
|
|
||||||
for (const structure of model.structures) {
|
|
||||||
structureMap.set(structure.name, structure);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectInheritedProperties(structure: Structure, visited = new Set<string>()): Property[] {
|
|
||||||
if (visited.has(structure.name)) {
|
|
||||||
return []; // Avoid circular dependencies
|
|
||||||
}
|
|
||||||
visited.add(structure.name);
|
|
||||||
|
|
||||||
const properties: Property[] = [];
|
|
||||||
const inheritanceTypes = [...(structure.extends || []), ...(structure.mixins || [])];
|
|
||||||
|
|
||||||
for (const type of inheritanceTypes) {
|
|
||||||
if (type.kind === "reference") {
|
|
||||||
const inheritedStructure = structureMap.get(type.name);
|
|
||||||
if (inheritedStructure) {
|
|
||||||
properties.push(
|
|
||||||
...collectInheritedProperties(inheritedStructure, new Set(visited)),
|
|
||||||
...inheritedStructure.properties,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inline inheritance for each structure
|
|
||||||
for (const structure of model.structures) {
|
|
||||||
const inheritedProperties = collectInheritedProperties(structure);
|
|
||||||
|
|
||||||
// Merge properties with structure's own properties taking precedence
|
|
||||||
const propertyMap = new Map<string, Property>();
|
|
||||||
|
|
||||||
inheritedProperties.forEach(prop => propertyMap.set(prop.name, prop));
|
|
||||||
structure.properties.forEach(prop => propertyMap.set(prop.name, prop));
|
|
||||||
|
|
||||||
structure.properties = Array.from(propertyMap.values());
|
|
||||||
structure.extends = undefined;
|
|
||||||
structure.mixins = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preprocess the model before proceeding
|
|
||||||
preprocessModel();
|
|
||||||
|
|
||||||
interface GoType {
|
|
||||||
name: string;
|
|
||||||
needsPointer: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TypeInfo {
|
|
||||||
types: Map<string, GoType>;
|
|
||||||
literalTypes: Map<string, string>;
|
|
||||||
unionTypes: Map<string, { name: string; type: Type; containedNull: boolean; }[]>;
|
|
||||||
typeAliasMap: Map<string, Type>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeInfo: TypeInfo = {
|
|
||||||
types: new Map(),
|
|
||||||
literalTypes: new Map(),
|
|
||||||
unionTypes: new Map(),
|
|
||||||
typeAliasMap: new Map(),
|
|
||||||
};
|
|
||||||
|
|
||||||
function titleCase(s: string) {
|
|
||||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveType(type: Type): GoType {
|
|
||||||
switch (type.kind) {
|
|
||||||
case "base":
|
|
||||||
switch (type.name) {
|
|
||||||
case "integer":
|
|
||||||
return { name: "int32", needsPointer: false };
|
|
||||||
case "uinteger":
|
|
||||||
return { name: "uint32", needsPointer: false };
|
|
||||||
case "string":
|
|
||||||
return { name: "string", needsPointer: false };
|
|
||||||
case "boolean":
|
|
||||||
return { name: "bool", needsPointer: false };
|
|
||||||
case "URI":
|
|
||||||
return { name: "URI", needsPointer: false };
|
|
||||||
case "DocumentUri":
|
|
||||||
return { name: "DocumentUri", needsPointer: false };
|
|
||||||
case "decimal":
|
|
||||||
return { name: "float64", needsPointer: false };
|
|
||||||
case "null":
|
|
||||||
return { name: "any", needsPointer: false };
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported base type: ${type.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "reference":
|
|
||||||
const typeAliasOverride = typeAliasOverrides.get(type.name);
|
|
||||||
if (typeAliasOverride) {
|
|
||||||
return typeAliasOverride;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a type alias that resolves to a union type
|
|
||||||
const aliasedType = typeInfo.typeAliasMap.get(type.name);
|
|
||||||
if (aliasedType) {
|
|
||||||
return resolveType(aliasedType);
|
|
||||||
}
|
|
||||||
|
|
||||||
let refType = typeInfo.types.get(type.name);
|
|
||||||
if (!refType) {
|
|
||||||
refType = { name: type.name, needsPointer: true };
|
|
||||||
typeInfo.types.set(type.name, refType);
|
|
||||||
}
|
|
||||||
return refType;
|
|
||||||
|
|
||||||
case "array": {
|
|
||||||
const elementType = resolveType(type.element);
|
|
||||||
const arrayTypeName = elementType.needsPointer
|
|
||||||
? `[]*${elementType.name}`
|
|
||||||
: `[]${elementType.name}`;
|
|
||||||
return {
|
|
||||||
name: arrayTypeName,
|
|
||||||
needsPointer: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case "map": {
|
|
||||||
const keyType = resolveType(type.key);
|
|
||||||
const valueType = resolveType(type.value);
|
|
||||||
const valueTypeName = valueType.needsPointer ? `*${valueType.name}` : valueType.name;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: `map[${keyType.name}]${valueTypeName}`,
|
|
||||||
needsPointer: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case "tuple": {
|
|
||||||
if (
|
|
||||||
type.items.length === 2 &&
|
|
||||||
type.items[0].kind === "base" && type.items[0].name === "uinteger" &&
|
|
||||||
type.items[1].kind === "base" && type.items[1].name === "uinteger"
|
|
||||||
) {
|
|
||||||
return { name: "[2]uint32", needsPointer: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Unsupported tuple type: " + JSON.stringify(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
case "stringLiteral": {
|
|
||||||
const typeName = `StringLiteral${titleCase(type.value)}`;
|
|
||||||
typeInfo.literalTypes.set(String(type.value), typeName);
|
|
||||||
return { name: typeName, needsPointer: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "integerLiteral": {
|
|
||||||
const typeName = `IntegerLiteral${type.value}`;
|
|
||||||
typeInfo.literalTypes.set(String(type.value), typeName);
|
|
||||||
return { name: typeName, needsPointer: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "booleanLiteral": {
|
|
||||||
const typeName = `BooleanLiteral${type.value ? "True" : "False"}`;
|
|
||||||
typeInfo.literalTypes.set(String(type.value), typeName);
|
|
||||||
return { name: typeName, needsPointer: false };
|
|
||||||
}
|
|
||||||
case "literal":
|
|
||||||
if (type.value.properties.length === 0) {
|
|
||||||
return { name: "struct{}", needsPointer: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Unexpected non-empty literal object: " + JSON.stringify(type.value));
|
|
||||||
|
|
||||||
case "or": {
|
|
||||||
return handleOrType(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported type kind: ${type.kind}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function flattenOrTypes(types: Type[]): Type[] {
|
|
||||||
const flattened = new Set<Type>();
|
|
||||||
|
|
||||||
for (const rawType of types) {
|
|
||||||
let type = rawType;
|
|
||||||
|
|
||||||
// Dereference reference types that point to OR types
|
|
||||||
if (rawType.kind === "reference") {
|
|
||||||
const aliasedType = typeInfo.typeAliasMap.get(rawType.name);
|
|
||||||
if (aliasedType && aliasedType.kind === "or") {
|
|
||||||
type = aliasedType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type.kind === "or") {
|
|
||||||
// Recursively flatten OR types
|
|
||||||
for (const subType of flattenOrTypes(type.items)) {
|
|
||||||
flattened.add(subType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
flattened.add(rawType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(flattened);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOrType(orType: OrType): GoType {
|
|
||||||
// First, flatten any nested OR types
|
|
||||||
const types = flattenOrTypes(orType.items);
|
|
||||||
|
|
||||||
// Check for nullable types (OR with null)
|
|
||||||
const nullIndex = types.findIndex(item => item.kind === "base" && item.name === "null");
|
|
||||||
let containedNull = nullIndex !== -1;
|
|
||||||
|
|
||||||
// If it's nullable, remove the null type from the list
|
|
||||||
let nonNullTypes = types;
|
|
||||||
if (containedNull) {
|
|
||||||
nonNullTypes = types.filter((_, i) => i !== nullIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no types remain after filtering null, this shouldn't happen
|
|
||||||
if (nonNullTypes.length === 0) {
|
|
||||||
throw new Error("Union type with only null is not supported: " + JSON.stringify(types));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even if only one type remains after filtering null, we still need to create a union type
|
|
||||||
// to preserve the nullable behavior (all fields nil = null)
|
|
||||||
|
|
||||||
let memberNames = nonNullTypes.map(type => {
|
|
||||||
if (type.kind === "reference") {
|
|
||||||
return type.name;
|
|
||||||
}
|
|
||||||
else if (type.kind === "base") {
|
|
||||||
return titleCase(type.name);
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
type.kind === "array" &&
|
|
||||||
(type.element.kind === "reference" || type.element.kind === "base")
|
|
||||||
) {
|
|
||||||
return `${titleCase(type.element.name)}s`;
|
|
||||||
}
|
|
||||||
else if (type.kind === "array") {
|
|
||||||
// Handle more complex array types
|
|
||||||
const elementType = resolveType(type.element);
|
|
||||||
return `${elementType.name}Array`;
|
|
||||||
}
|
|
||||||
else if (type.kind === "literal" && type.value.properties.length === 0) {
|
|
||||||
return "EmptyObject";
|
|
||||||
}
|
|
||||||
else if (type.kind === "tuple") {
|
|
||||||
return "Tuple";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error(`Unsupported type kind in union: ${type.kind}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find longest common prefix of member names chunked by PascalCase
|
|
||||||
function findLongestCommonPrefix(names: string[]): string {
|
|
||||||
if (names.length === 0) return "";
|
|
||||||
if (names.length === 1) return "";
|
|
||||||
|
|
||||||
// Split each name into PascalCase chunks
|
|
||||||
function splitPascalCase(name: string): string[] {
|
|
||||||
const chunks: string[] = [];
|
|
||||||
let currentChunk = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < name.length; i++) {
|
|
||||||
const char = name[i];
|
|
||||||
if (char >= "A" && char <= "Z" && currentChunk.length > 0) {
|
|
||||||
// Start of a new chunk
|
|
||||||
chunks.push(currentChunk);
|
|
||||||
currentChunk = char;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
currentChunk += char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentChunk.length > 0) {
|
|
||||||
chunks.push(currentChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allChunks = names.map(splitPascalCase);
|
|
||||||
const minChunkLength = Math.min(...allChunks.map(chunks => chunks.length));
|
|
||||||
|
|
||||||
// Find the longest common prefix of chunks
|
|
||||||
let commonChunks: string[] = [];
|
|
||||||
for (let i = 0; i < minChunkLength; i++) {
|
|
||||||
const chunk = allChunks[0][i];
|
|
||||||
if (allChunks.every(chunks => chunks[i] === chunk)) {
|
|
||||||
commonChunks.push(chunk);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return commonChunks.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
const commonPrefix = findLongestCommonPrefix(memberNames);
|
|
||||||
|
|
||||||
let unionTypeName = "";
|
|
||||||
|
|
||||||
if (commonPrefix.length > 0) {
|
|
||||||
const trimmedMemberNames = memberNames.map(name => name.slice(commonPrefix.length));
|
|
||||||
if (trimmedMemberNames.every(name => name)) {
|
|
||||||
unionTypeName = commonPrefix + trimmedMemberNames.join("Or");
|
|
||||||
memberNames = trimmedMemberNames;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unionTypeName = memberNames.join("Or");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unionTypeName = memberNames.join("Or");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containedNull) {
|
|
||||||
unionTypeName += "OrNull";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
containedNull = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const union = memberNames.map((name, i) => ({ name, type: nonNullTypes[i], containedNull }));
|
|
||||||
|
|
||||||
typeInfo.unionTypes.set(unionTypeName, union);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: unionTypeName,
|
|
||||||
needsPointer: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeAliasOverrides = new Map([
|
|
||||||
["LSPAny", { name: "any", needsPointer: false }],
|
|
||||||
["LSPArray", { name: "[]any", needsPointer: false }],
|
|
||||||
["LSPObject", { name: "map[string]any", needsPointer: false }],
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First pass: Resolve all type information
|
|
||||||
*/
|
|
||||||
function collectTypeDefinitions() {
|
|
||||||
// Process all enumerations first to make them available for struct fields
|
|
||||||
for (const enumeration of model.enumerations) {
|
|
||||||
typeInfo.types.set(enumeration.name, {
|
|
||||||
name: enumeration.name,
|
|
||||||
needsPointer: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const valueTypes = new Set([
|
|
||||||
"Position",
|
|
||||||
"Range",
|
|
||||||
"Location",
|
|
||||||
"Color",
|
|
||||||
"TextDocumentIdentifier",
|
|
||||||
"NotebookDocumentIdentifier",
|
|
||||||
"PreviousResultId",
|
|
||||||
"VersionedNotebookDocumentIdentifier",
|
|
||||||
"VersionedTextDocumentIdentifier",
|
|
||||||
"OptionalVersionedTextDocumentIdentifier",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Process all structures
|
|
||||||
for (const structure of model.structures) {
|
|
||||||
typeInfo.types.set(structure.name, {
|
|
||||||
name: structure.name,
|
|
||||||
needsPointer: !valueTypes.has(structure.name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process all type aliases
|
|
||||||
for (const typeAlias of model.typeAliases) {
|
|
||||||
if (typeAliasOverrides.has(typeAlias.name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the alias mapping so we can resolve it later
|
|
||||||
typeInfo.typeAliasMap.set(typeAlias.name, typeAlias.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDocumentation(s: string | undefined): string {
|
|
||||||
if (!s) return "";
|
|
||||||
|
|
||||||
let lines: string[] = [];
|
|
||||||
|
|
||||||
for (let line of s.split("\n")) {
|
|
||||||
line = line.trimEnd();
|
|
||||||
line = line.replace(/(\w ) +/g, "$1");
|
|
||||||
line = line.replace(/\{@link(?:code)?.*?([^} ]+)\}/g, "$1");
|
|
||||||
line = line.replace(/^@(since|proposed|deprecated)(.*)/, (_, tag, rest) => {
|
|
||||||
lines.push("");
|
|
||||||
return `${titleCase(tag)}${rest ? ":" + rest : "."}`;
|
|
||||||
});
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter out contiguous empty lines
|
|
||||||
while (true) {
|
|
||||||
const toRemove = lines.findIndex((line, index) => {
|
|
||||||
if (line) return false;
|
|
||||||
if (index === 0) return true;
|
|
||||||
if (index === lines.length - 1) return true;
|
|
||||||
return !(lines[index - 1] && lines[index + 1]);
|
|
||||||
});
|
|
||||||
if (toRemove === -1) break;
|
|
||||||
lines.splice(toRemove, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.length > 0 ? "// " + lines.join("\n// ") + "\n" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function methodNameIdentifier(name: string) {
|
|
||||||
return name.split("/").map(v => v === "$" ? "" : titleCase(v)).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the Go code
|
|
||||||
*/
|
|
||||||
function generateCode() {
|
|
||||||
const parts: string[] = [];
|
|
||||||
|
|
||||||
function write(s: string) {
|
|
||||||
parts.push(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeLine(s = "") {
|
|
||||||
parts.push(s + "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// File header
|
|
||||||
writeLine("// Code generated by generate.mts; DO NOT EDIT.");
|
|
||||||
writeLine("");
|
|
||||||
writeLine("package lsproto");
|
|
||||||
writeLine("");
|
|
||||||
writeLine(`import (`);
|
|
||||||
writeLine(`\t"fmt"`);
|
|
||||||
writeLine("");
|
|
||||||
writeLine(`\t"github.com/go-json-experiment/json"`);
|
|
||||||
writeLine(`\t"github.com/go-json-experiment/json/jsontext"`);
|
|
||||||
writeLine(`)`);
|
|
||||||
writeLine("");
|
|
||||||
writeLine("// Meta model version " + model.metaData.version);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
// Generate structures
|
|
||||||
writeLine("// Structures\n");
|
|
||||||
|
|
||||||
for (const structure of model.structures) {
|
|
||||||
function generateStructFields(name: string, includeDocumentation: boolean) {
|
|
||||||
if (includeDocumentation) {
|
|
||||||
write(formatDocumentation(structure.documentation));
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(`type ${name} struct {`);
|
|
||||||
|
|
||||||
// Properties are now inlined, no need to embed extends/mixins
|
|
||||||
for (const prop of structure.properties) {
|
|
||||||
if (includeDocumentation) {
|
|
||||||
write(formatDocumentation(prop.documentation));
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = resolveType(prop.type);
|
|
||||||
const goType = prop.optional || type.needsPointer ? `*${type.name}` : type.name;
|
|
||||||
|
|
||||||
writeLine(`\t${titleCase(prop.name)} ${goType} \`json:"${prop.name}${prop.optional ? ",omitzero" : ""}"\``);
|
|
||||||
|
|
||||||
if (includeDocumentation) {
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine("}");
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
generateStructFields(structure.name, true);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
if (hasTextDocumentURI(structure)) {
|
|
||||||
// Generate TextDocumentURI method
|
|
||||||
writeLine(`func (s *${structure.name}) TextDocumentURI() DocumentUri {`);
|
|
||||||
writeLine(`\treturn s.TextDocument.Uri`);
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate UnmarshalJSONFrom method for structure validation
|
|
||||||
const requiredProps = structure.properties?.filter(p => !p.optional) || [];
|
|
||||||
if (requiredProps.length > 0) {
|
|
||||||
writeLine(`\tvar _ json.UnmarshalerFrom = (*${structure.name})(nil)`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`func (s *${structure.name}) UnmarshalJSONFrom(dec *jsontext.Decoder) error {`);
|
|
||||||
writeLine(`\tvar (`);
|
|
||||||
for (const prop of requiredProps) {
|
|
||||||
writeLine(`\t\tseen${titleCase(prop.name)} bool`);
|
|
||||||
}
|
|
||||||
writeLine(`\t)`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`\tif k := dec.PeekKind(); k != '{' {`);
|
|
||||||
writeLine(`\t\treturn fmt.Errorf("expected object start, but encountered %v", k)`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine(`\tif _, err := dec.ReadToken(); err != nil {`);
|
|
||||||
writeLine(`\t\treturn err`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`\tfor dec.PeekKind() != '}' {`);
|
|
||||||
writeLine("name, err := dec.ReadValue()");
|
|
||||||
writeLine(`\t\tif err != nil {`);
|
|
||||||
writeLine(`\t\t\treturn err`);
|
|
||||||
writeLine(`\t\t}`);
|
|
||||||
writeLine(`\t\tswitch string(name) {`);
|
|
||||||
|
|
||||||
for (const prop of structure.properties) {
|
|
||||||
writeLine(`\t\tcase \`"${prop.name}"\`:`);
|
|
||||||
if (!prop.optional) {
|
|
||||||
writeLine(`\t\t\tseen${titleCase(prop.name)} = true`);
|
|
||||||
}
|
|
||||||
writeLine(`\t\t\tif err := json.UnmarshalDecode(dec, &s.${titleCase(prop.name)}); err != nil {`);
|
|
||||||
writeLine(`\t\t\t\treturn err`);
|
|
||||||
writeLine(`\t\t\t}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(`\t\tdefault:`);
|
|
||||||
writeLine(`\t\t// Ignore unknown properties.`);
|
|
||||||
writeLine(`\t\t}`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`\tif _, err := dec.ReadToken(); err != nil {`);
|
|
||||||
writeLine(`\t\treturn err`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
for (const prop of requiredProps) {
|
|
||||||
writeLine(`\tif !seen${titleCase(prop.name)} {`);
|
|
||||||
writeLine(`\t\treturn fmt.Errorf("required property '${prop.name}' is missing")`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine("");
|
|
||||||
writeLine(`\treturn nil`);
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate enumerations
|
|
||||||
writeLine("// Enumerations\n");
|
|
||||||
|
|
||||||
for (const enumeration of model.enumerations) {
|
|
||||||
write(formatDocumentation(enumeration.documentation));
|
|
||||||
|
|
||||||
let baseType;
|
|
||||||
switch (enumeration.type.name) {
|
|
||||||
case "string":
|
|
||||||
baseType = "string";
|
|
||||||
break;
|
|
||||||
case "integer":
|
|
||||||
baseType = "int32";
|
|
||||||
break;
|
|
||||||
case "uinteger":
|
|
||||||
baseType = "uint32";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported enum type: ${enumeration.type.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(`type ${enumeration.name} ${baseType}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
// Get the pre-processed enum entries map that avoids duplicates
|
|
||||||
|
|
||||||
const enumValues = enumeration.values.map(value => ({
|
|
||||||
value: String(value.value),
|
|
||||||
identifier: `${enumeration.name}${value.name}`,
|
|
||||||
documentation: value.documentation,
|
|
||||||
deprecated: value.deprecated,
|
|
||||||
}));
|
|
||||||
|
|
||||||
writeLine("const (");
|
|
||||||
|
|
||||||
// Process entries with unique identifiers
|
|
||||||
for (const entry of enumValues) {
|
|
||||||
write(formatDocumentation(entry.documentation));
|
|
||||||
|
|
||||||
let valueLiteral;
|
|
||||||
// Handle string values
|
|
||||||
if (enumeration.type.name === "string") {
|
|
||||||
valueLiteral = `"${entry.value.replace(/^"|"$/g, "")}"`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
valueLiteral = entry.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(`\t${entry.identifier} ${enumeration.name} = ${valueLiteral}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(")");
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestsAndNotifications: (Request | Notification)[] = [...model.requests, ...model.notifications];
|
|
||||||
|
|
||||||
// Generate unmarshalParams function
|
|
||||||
writeLine("func unmarshalParams(method Method, data []byte) (any, error) {");
|
|
||||||
writeLine("\tswitch method {");
|
|
||||||
|
|
||||||
// Requests and notifications
|
|
||||||
for (const request of requestsAndNotifications) {
|
|
||||||
const methodName = methodNameIdentifier(request.method);
|
|
||||||
|
|
||||||
if (!request.params) {
|
|
||||||
writeLine(`\tcase Method${methodName}:`);
|
|
||||||
writeLine(`\t\treturn unmarshalEmpty(data)`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (Array.isArray(request.params)) {
|
|
||||||
throw new Error("Unexpected array type for request params: " + JSON.stringify(request.params));
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedType = resolveType(request.params);
|
|
||||||
|
|
||||||
writeLine(`\tcase Method${methodName}:`);
|
|
||||||
if (resolvedType.name === "any") {
|
|
||||||
writeLine(`\t\treturn unmarshalAny(data)`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
writeLine(`\t\treturn unmarshalPtrTo[${resolvedType.name}](data)`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine("\tdefault:");
|
|
||||||
writeLine(`\t\treturn unmarshalAny(data)`);
|
|
||||||
writeLine("\t}");
|
|
||||||
writeLine("}");
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine("// Methods");
|
|
||||||
writeLine("const (");
|
|
||||||
for (const request of requestsAndNotifications) {
|
|
||||||
write(formatDocumentation(request.documentation));
|
|
||||||
|
|
||||||
const methodName = methodNameIdentifier(request.method);
|
|
||||||
|
|
||||||
writeLine(`\tMethod${methodName} Method = "${request.method}"`);
|
|
||||||
}
|
|
||||||
writeLine(")");
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
// Generate request response types
|
|
||||||
writeLine("// Request response types");
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
for (const request of requestsAndNotifications) {
|
|
||||||
const methodName = methodNameIdentifier(request.method);
|
|
||||||
|
|
||||||
let responseTypeName: string | undefined;
|
|
||||||
|
|
||||||
if ("result" in request) {
|
|
||||||
if (request.typeName && request.typeName.endsWith("Request")) {
|
|
||||||
responseTypeName = request.typeName.replace(/Request$/, "Response");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
responseTypeName = `${methodName}Response`;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(`// Response type for \`${request.method}\``);
|
|
||||||
|
|
||||||
// Special case for response types that are explicitly base type "null"
|
|
||||||
if (request.result.kind === "base" && request.result.name === "null") {
|
|
||||||
writeLine(`type ${responseTypeName} = Null`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const resultType = resolveType(request.result);
|
|
||||||
const goType = resultType.needsPointer ? `*${resultType.name}` : resultType.name;
|
|
||||||
writeLine(`type ${responseTypeName} = ${goType}`);
|
|
||||||
}
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(request.params)) {
|
|
||||||
throw new Error("Unexpected request params for " + methodName + ": " + JSON.stringify(request.params));
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramType = request.params ? resolveType(request.params) : undefined;
|
|
||||||
const paramGoType = paramType ? (paramType.needsPointer ? `*${paramType.name}` : paramType.name) : "any";
|
|
||||||
|
|
||||||
writeLine(`// Type mapping info for \`${request.method}\``);
|
|
||||||
if (responseTypeName) {
|
|
||||||
writeLine(`var ${methodName}Info = RequestInfo[${paramGoType}, ${responseTypeName}]{Method: Method${methodName}}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
writeLine(`var ${methodName}Info = NotificationInfo[${paramGoType}]{Method: Method${methodName}}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate union types
|
|
||||||
writeLine("// Union types\n");
|
|
||||||
|
|
||||||
for (const [name, members] of typeInfo.unionTypes.entries()) {
|
|
||||||
writeLine(`type ${name} struct {`);
|
|
||||||
const uniqueTypeFields = new Map(); // Maps type name -> field name
|
|
||||||
|
|
||||||
for (const member of members) {
|
|
||||||
const type = resolveType(member.type);
|
|
||||||
const memberType = type.name;
|
|
||||||
|
|
||||||
// If this type name already exists in our map, skip it
|
|
||||||
if (!uniqueTypeFields.has(memberType)) {
|
|
||||||
const fieldName = titleCase(member.name);
|
|
||||||
uniqueTypeFields.set(memberType, fieldName);
|
|
||||||
writeLine(`\t${fieldName} *${memberType}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
// Get the field names and types for marshal/unmarshal methods
|
|
||||||
const fieldEntries = Array.from(uniqueTypeFields.entries()).map(([typeName, fieldName]) => ({ fieldName, typeName }));
|
|
||||||
|
|
||||||
// Marshal method
|
|
||||||
writeLine(`var _ json.MarshalerTo = (*${name})(nil)`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`func (o *${name}) MarshalJSONTo(enc *jsontext.Encoder) error {`);
|
|
||||||
|
|
||||||
// Determine if this union contained null (check if any member has containedNull = true)
|
|
||||||
const unionContainedNull = members.some(member => member.containedNull);
|
|
||||||
if (unionContainedNull) {
|
|
||||||
write(`\tassertAtMostOne("more than one element of ${name} is set", `);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
write(`\tassertOnlyOne("exactly one element of ${name} should be set", `);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create assertion to ensure at most one field is set at a time
|
|
||||||
|
|
||||||
// Write the assertion conditions
|
|
||||||
for (let i = 0; i < fieldEntries.length; i++) {
|
|
||||||
if (i > 0) write(", ");
|
|
||||||
write(`o.${fieldEntries[i].fieldName} != nil`);
|
|
||||||
}
|
|
||||||
writeLine(`)`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
for (const entry of fieldEntries) {
|
|
||||||
writeLine(`\tif o.${entry.fieldName} != nil {`);
|
|
||||||
writeLine(`\t\treturn json.MarshalEncode(enc, o.${entry.fieldName})`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all fields are nil, marshal as null (only for unions that can contain null)
|
|
||||||
if (unionContainedNull) {
|
|
||||||
writeLine(`\treturn enc.WriteToken(jsontext.Null)`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
writeLine(`\tpanic("unreachable")`);
|
|
||||||
}
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
// Unmarshal method
|
|
||||||
writeLine(`var _ json.UnmarshalerFrom = (*${name})(nil)`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`func (o *${name}) UnmarshalJSONFrom(dec *jsontext.Decoder) error {`);
|
|
||||||
writeLine(`\t*o = ${name}{}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine("\tdata, err := dec.ReadValue()");
|
|
||||||
writeLine("\tif err != nil {");
|
|
||||||
writeLine("\t\treturn err");
|
|
||||||
writeLine("\t}");
|
|
||||||
|
|
||||||
if (unionContainedNull) {
|
|
||||||
writeLine(`\tif string(data) == "null" {`);
|
|
||||||
writeLine(`\t\treturn nil`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of fieldEntries) {
|
|
||||||
writeLine(`\tvar v${entry.fieldName} ${entry.typeName}`);
|
|
||||||
writeLine(`\tif err := json.Unmarshal(data, &v${entry.fieldName}); err == nil {`);
|
|
||||||
writeLine(`\t\to.${entry.fieldName} = &v${entry.fieldName}`);
|
|
||||||
writeLine(`\t\treturn nil`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match the error format from the original script
|
|
||||||
writeLine(`\treturn fmt.Errorf("invalid ${name}: %s", data)`);
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate literal types
|
|
||||||
writeLine("// Literal types\n");
|
|
||||||
|
|
||||||
for (const [value, name] of typeInfo.literalTypes.entries()) {
|
|
||||||
const jsonValue = JSON.stringify(value);
|
|
||||||
|
|
||||||
writeLine(`// ${name} is a literal type for ${jsonValue}`);
|
|
||||||
writeLine(`type ${name} struct{}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`var _ json.MarshalerTo = ${name}{}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`func (o ${name}) MarshalJSONTo(enc *jsontext.Encoder) error {`);
|
|
||||||
writeLine(`\treturn enc.WriteValue(jsontext.Value(\`${jsonValue}\`))`);
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`var _ json.UnmarshalerFrom = &${name}{}`);
|
|
||||||
writeLine("");
|
|
||||||
|
|
||||||
writeLine(`func (o *${name}) UnmarshalJSONFrom(dec *jsontext.Decoder) error {`);
|
|
||||||
writeLine(`\tv, err := dec.ReadValue();`);
|
|
||||||
writeLine(`\tif err != nil {`);
|
|
||||||
writeLine(`\t\treturn err`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine(`\tif string(v) != \`${jsonValue}\` {`);
|
|
||||||
writeLine(`\t\treturn fmt.Errorf("expected ${name} value %s, got %s", \`${jsonValue}\`, v)`);
|
|
||||||
writeLine(`\t}`);
|
|
||||||
writeLine(`\treturn nil`);
|
|
||||||
writeLine(`}`);
|
|
||||||
writeLine("");
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasTextDocumentURI(structure: Structure) {
|
|
||||||
return structure.properties?.some(p =>
|
|
||||||
!p.optional &&
|
|
||||||
p.name === "textDocument" &&
|
|
||||||
p.type.kind === "reference" &&
|
|
||||||
p.type.name === "TextDocumentIdentifier"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function
|
|
||||||
*/
|
|
||||||
function main() {
|
|
||||||
try {
|
|
||||||
collectTypeDefinitions();
|
|
||||||
const generatedCode = generateCode();
|
|
||||||
fs.writeFileSync(out, generatedCode);
|
|
||||||
|
|
||||||
// Format with gofmt
|
|
||||||
const gofmt = which.sync("go");
|
|
||||||
cp.execFileSync(gofmt, ["tool", "mvdan.cc/gofumpt", "-lang=go1.25", "-w", out]);
|
|
||||||
|
|
||||||
console.log(`Successfully generated ${out}`);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error("Error generating code:", error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@ -1,609 +0,0 @@
|
|||||||
/* --------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
* ------------------------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
export type BaseTypes = 'URI' | 'DocumentUri' | 'integer' | 'uinteger' | 'decimal' | 'RegExp' | 'string' | 'boolean' | 'null';
|
|
||||||
|
|
||||||
export type TypeKind = 'base' | 'reference' | 'array' | 'map' | 'and' | 'or' | 'tuple' | 'literal' | 'stringLiteral' | 'integerLiteral' | 'booleanLiteral';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates in which direction a message is sent in the protocol.
|
|
||||||
*/
|
|
||||||
export type MessageDirection = 'clientToServer' | 'serverToClient' | 'both';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a base type like `string` or `DocumentUri`.
|
|
||||||
*/
|
|
||||||
export type BaseType = {
|
|
||||||
kind: 'base';
|
|
||||||
name: BaseTypes;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a reference to another type (e.g. `TextDocument`).
|
|
||||||
* This is either a `Structure`, a `Enumeration` or a `TypeAlias`
|
|
||||||
* in the same meta model.
|
|
||||||
*/
|
|
||||||
export type ReferenceType = {
|
|
||||||
kind: 'reference';
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an array type (e.g. `TextDocument[]`).
|
|
||||||
*/
|
|
||||||
export type ArrayType = {
|
|
||||||
kind: 'array';
|
|
||||||
element: Type;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a type that can be used as a key in a
|
|
||||||
* map type. If a reference type is used then the
|
|
||||||
* type must either resolve to a `string` or `integer`
|
|
||||||
* type. (e.g. `type ChangeAnnotationIdentifier === string`).
|
|
||||||
*/
|
|
||||||
export type MapKeyType = { kind: 'base'; name: 'URI' | 'DocumentUri' | 'string' | 'integer' } | ReferenceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a JSON object map
|
|
||||||
* (e.g. `interface Map<K extends string | integer, V> { [key: K] => V; }`).
|
|
||||||
*/
|
|
||||||
export type MapType = {
|
|
||||||
kind: 'map';
|
|
||||||
key: MapKeyType;
|
|
||||||
value: Type;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an `and`type
|
|
||||||
* (e.g. TextDocumentParams & WorkDoneProgressParams`).
|
|
||||||
*/
|
|
||||||
export type AndType = {
|
|
||||||
kind: 'and';
|
|
||||||
items: Type[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an `or` type
|
|
||||||
* (e.g. `Location | LocationLink`).
|
|
||||||
*/
|
|
||||||
export type OrType = {
|
|
||||||
kind: 'or';
|
|
||||||
items: Type[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a `tuple` type
|
|
||||||
* (e.g. `[integer, integer]`).
|
|
||||||
*/
|
|
||||||
export type TupleType = {
|
|
||||||
kind: 'tuple';
|
|
||||||
items: Type[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a literal structure
|
|
||||||
* (e.g. `property: { start: uinteger; end: uinteger; }`).
|
|
||||||
*/
|
|
||||||
export type StructureLiteralType = {
|
|
||||||
kind: 'literal';
|
|
||||||
value: StructureLiteral;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a string literal type
|
|
||||||
* (e.g. `kind: 'rename'`).
|
|
||||||
*/
|
|
||||||
export type StringLiteralType = {
|
|
||||||
kind: 'stringLiteral';
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type IntegerLiteralType = {
|
|
||||||
/**
|
|
||||||
* Represents an integer literal type
|
|
||||||
* (e.g. `kind: 1`).
|
|
||||||
*/
|
|
||||||
kind: 'integerLiteral';
|
|
||||||
value: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a boolean literal type
|
|
||||||
* (e.g. `kind: true`).
|
|
||||||
*/
|
|
||||||
export type BooleanLiteralType = {
|
|
||||||
kind: 'booleanLiteral';
|
|
||||||
value: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Type = BaseType | ReferenceType | ArrayType | MapType | AndType | OrType | TupleType | StructureLiteralType | StringLiteralType | IntegerLiteralType | BooleanLiteralType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a LSP request
|
|
||||||
*/
|
|
||||||
export type Request = {
|
|
||||||
/**
|
|
||||||
* The request's method name.
|
|
||||||
*/
|
|
||||||
method: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type name of the request if any.
|
|
||||||
*/
|
|
||||||
typeName?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parameter type(s) if any.
|
|
||||||
*/
|
|
||||||
params?: Type | Type[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The result type.
|
|
||||||
*/
|
|
||||||
result: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional partial result type if the request
|
|
||||||
* supports partial result reporting.
|
|
||||||
*/
|
|
||||||
partialResult?: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional error data type.
|
|
||||||
*/
|
|
||||||
errorData?: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional a dynamic registration method if it
|
|
||||||
* different from the request's method.
|
|
||||||
*/
|
|
||||||
registrationMethod?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional registration options if the request
|
|
||||||
* supports dynamic registration.
|
|
||||||
*/
|
|
||||||
registrationOptions?: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The direction in which this request is sent
|
|
||||||
* in the protocol.
|
|
||||||
*/
|
|
||||||
messageDirection: MessageDirection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation;
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this request is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed feature. If omitted
|
|
||||||
* the feature is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the request is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a LSP notification
|
|
||||||
*/
|
|
||||||
export type Notification = {
|
|
||||||
/**
|
|
||||||
* The notifications's method name.
|
|
||||||
*/
|
|
||||||
method: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type name of the notifications if any.
|
|
||||||
*/
|
|
||||||
typeName?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parameter type(s) if any.
|
|
||||||
*/
|
|
||||||
params?: Type | Type[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional a dynamic registration method if it
|
|
||||||
* different from the notifications's method.
|
|
||||||
*/
|
|
||||||
registrationMethod?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional registration options if the notification
|
|
||||||
* supports dynamic registration.
|
|
||||||
*/
|
|
||||||
registrationOptions?: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The direction in which this notification is sent
|
|
||||||
* in the protocol.
|
|
||||||
*/
|
|
||||||
messageDirection: MessageDirection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation;
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this notification is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed notification. If omitted
|
|
||||||
* the notification is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the notification is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an object property.
|
|
||||||
*/
|
|
||||||
export type Property = {
|
|
||||||
/**
|
|
||||||
* The property name;
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the property
|
|
||||||
*/
|
|
||||||
type: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the property is optional. If
|
|
||||||
* omitted, the property is mandatory.
|
|
||||||
*/
|
|
||||||
optional?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation.
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this property is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed property. If omitted,
|
|
||||||
* the structure is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the property is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the structure of an object literal.
|
|
||||||
*/
|
|
||||||
export type Structure = {
|
|
||||||
/**
|
|
||||||
* The name of the structure.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structures extended from. This structures form
|
|
||||||
* a polymorphic type hierarchy.
|
|
||||||
*/
|
|
||||||
extends?: Type[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structures to mix in. The properties of these
|
|
||||||
* structures are `copied` into this structure.
|
|
||||||
* Mixins don't form a polymorphic type hierarchy in
|
|
||||||
* LSP.
|
|
||||||
*/
|
|
||||||
mixins?: Type[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The properties.
|
|
||||||
*/
|
|
||||||
properties: Property[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation;
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this structure is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed structure. If omitted,
|
|
||||||
* the structure is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the structure is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines an unnamed structure of an object literal.
|
|
||||||
*/
|
|
||||||
export type StructureLiteral = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The properties.
|
|
||||||
*/
|
|
||||||
properties: Property[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation.
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this structure is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed structure. If omitted,
|
|
||||||
* the structure is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the literal is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a type alias.
|
|
||||||
* (e.g. `type Definition = Location | LocationLink`)
|
|
||||||
*/
|
|
||||||
export type TypeAlias = {
|
|
||||||
/**
|
|
||||||
* The name of the type alias.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The aliased type.
|
|
||||||
*/
|
|
||||||
type: Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation.
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this structure is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed type alias. If omitted,
|
|
||||||
* the type alias is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the type alias is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines an enumeration entry.
|
|
||||||
*/
|
|
||||||
export type EnumerationEntry = {
|
|
||||||
/**
|
|
||||||
* The name of the enum item.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The value.
|
|
||||||
*/
|
|
||||||
value: string | number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation.
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this enumeration entry is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed enumeration entry. If omitted,
|
|
||||||
* the enumeration entry is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the enum entry is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EnumerationType = { kind: 'base'; name: 'string' | 'integer' | 'uinteger' };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines an enumeration.
|
|
||||||
*/
|
|
||||||
export type Enumeration = {
|
|
||||||
/**
|
|
||||||
* The name of the enumeration.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the elements.
|
|
||||||
*/
|
|
||||||
type: EnumerationType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The enum values.
|
|
||||||
*/
|
|
||||||
values: EnumerationEntry[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the enumeration supports custom values (e.g. values which are not
|
|
||||||
* part of the set defined in `values`). If omitted no custom values are
|
|
||||||
* supported.
|
|
||||||
*/
|
|
||||||
supportsCustomValues?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional documentation.
|
|
||||||
*/
|
|
||||||
documentation?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since when (release number) this enumeration is
|
|
||||||
* available. Is undefined if not known.
|
|
||||||
*/
|
|
||||||
since?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All since tags in case there was more than one tag.
|
|
||||||
* Is undefined if not known.
|
|
||||||
*/
|
|
||||||
sinceTags?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this is a proposed enumeration. If omitted,
|
|
||||||
* the enumeration is final.
|
|
||||||
*/
|
|
||||||
proposed?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the enumeration is deprecated or not. If deprecated
|
|
||||||
* the property contains the deprecation message.
|
|
||||||
*/
|
|
||||||
deprecated?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MetaData = {
|
|
||||||
/**
|
|
||||||
* The protocol version.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual meta model.
|
|
||||||
*/
|
|
||||||
export type MetaModel = {
|
|
||||||
/**
|
|
||||||
* Additional meta data.
|
|
||||||
*/
|
|
||||||
metaData: MetaData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The requests.
|
|
||||||
*/
|
|
||||||
requests: Request[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The notifications.
|
|
||||||
*/
|
|
||||||
notifications: Notification[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The structures.
|
|
||||||
*/
|
|
||||||
structures: Structure[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The enumerations.
|
|
||||||
*/
|
|
||||||
enumerations: Enumeration[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type aliases.
|
|
||||||
*/
|
|
||||||
typeAliases: TypeAlias[];
|
|
||||||
};
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "NodeNext",
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"erasableSyntaxOnly": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"types": ["node"],
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"*.mts",
|
|
||||||
"*.mjs"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
package lsproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://microsoft.github.io/language-server-protocol/specifications/base/0.9/specification/
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidHeader = errors.New("lsp: invalid header")
|
|
||||||
ErrInvalidContentLength = errors.New("lsp: invalid content length")
|
|
||||||
ErrNoContentLength = errors.New("lsp: no content length")
|
|
||||||
)
|
|
||||||
|
|
||||||
type BaseReader struct {
|
|
||||||
r *bufio.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBaseReader(r io.Reader) *BaseReader {
|
|
||||||
return &BaseReader{
|
|
||||||
r: bufio.NewReader(r),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BaseReader) Read() ([]byte, error) {
|
|
||||||
var contentLength int64
|
|
||||||
|
|
||||||
for {
|
|
||||||
line, err := r.r.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("lsp: read header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.Equal(line, []byte("\r\n")) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
key, value, ok := bytes.Cut(line, []byte(":"))
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("%w: %q", ErrInvalidHeader, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.Equal(key, []byte("Content-Length")) {
|
|
||||||
contentLength, err = strconv.ParseInt(string(bytes.TrimSpace(value)), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: parse error: %w", ErrInvalidContentLength, err)
|
|
||||||
}
|
|
||||||
if contentLength < 0 {
|
|
||||||
return nil, fmt.Errorf("%w: negative value %d", ErrInvalidContentLength, contentLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if contentLength <= 0 {
|
|
||||||
return nil, ErrNoContentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]byte, contentLength)
|
|
||||||
if _, err := io.ReadFull(r.r, data); err != nil {
|
|
||||||
return nil, fmt.Errorf("lsp: read content: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseWriter struct {
|
|
||||||
w *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBaseWriter(w io.Writer) *BaseWriter {
|
|
||||||
return &BaseWriter{
|
|
||||||
w: bufio.NewWriter(w),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BaseWriter) Write(data []byte) error {
|
|
||||||
if _, err := fmt.Fprintf(w.w, "Content-Length: %d\r\n\r\n", len(data)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.w.Write(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorCode struct { //nolint:errname
|
|
||||||
Name string
|
|
||||||
Code int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrorCode) Error() string {
|
|
||||||
return e.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Defined by JSON-RPC
|
|
||||||
ErrParseError = &ErrorCode{"ParseError", -32700}
|
|
||||||
ErrInvalidRequest = &ErrorCode{"InvalidRequest", -32600}
|
|
||||||
ErrMethodNotFound = &ErrorCode{"MethodNotFound", -32601}
|
|
||||||
ErrInvalidParams = &ErrorCode{"InvalidParams", -32602}
|
|
||||||
ErrInternalError = &ErrorCode{"InternalError", -32603}
|
|
||||||
|
|
||||||
// Error code indicating that a server received a notification or
|
|
||||||
// request before the server has received the `initialize` request.
|
|
||||||
ErrServerNotInitialized = &ErrorCode{"ServerNotInitialized", -32002}
|
|
||||||
ErrUnknownErrorCode = &ErrorCode{"UnknownErrorCode", -32001}
|
|
||||||
|
|
||||||
// A request failed but it was syntactically correct, e.g the
|
|
||||||
// method name was known and the parameters were valid. The error
|
|
||||||
// message should contain human readable information about why
|
|
||||||
// the request failed.
|
|
||||||
ErrRequestFailed = &ErrorCode{"RequestFailed", -32803}
|
|
||||||
|
|
||||||
// The server cancelled the request. This error code should
|
|
||||||
// only be used for requests that explicitly support being
|
|
||||||
// server cancellable.
|
|
||||||
ErrServerCancelled = &ErrorCode{"ServerCancelled", -32802}
|
|
||||||
|
|
||||||
// The server detected that the content of a document got
|
|
||||||
// modified outside normal conditions. A server should
|
|
||||||
// NOT send this error code if it detects a content change
|
|
||||||
// in it unprocessed messages. The result even computed
|
|
||||||
// on an older state might still be useful for the client.
|
|
||||||
//
|
|
||||||
// If a client decides that a result is not of any use anymore
|
|
||||||
// the client should cancel the request.
|
|
||||||
ErrContentModified = &ErrorCode{"ContentModified", -32801}
|
|
||||||
|
|
||||||
// The client has canceled a request and a server has detected
|
|
||||||
// the cancel.
|
|
||||||
ErrRequestCancelled = &ErrorCode{"RequestCancelled", -32800}
|
|
||||||
)
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
package lsproto_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBaseReader(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []byte
|
|
||||||
value []byte
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
input: []byte("Content-Length: 0\r\n\r\n"),
|
|
||||||
err: "lsp: no content length",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "early end",
|
|
||||||
input: []byte("oops"),
|
|
||||||
err: "EOF",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "negative length",
|
|
||||||
input: []byte("Content-Length: -1\r\n\r\n"),
|
|
||||||
err: "lsp: invalid content length: negative value -1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid content",
|
|
||||||
input: []byte("Content-Length: 1\r\n\r\n{"),
|
|
||||||
value: []byte("{"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid content",
|
|
||||||
input: []byte("Content-Length: 2\r\n\r\n{}"),
|
|
||||||
value: []byte("{}"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "extra header values",
|
|
||||||
input: []byte("Content-Length: 2\r\nExtra: 1\r\n\r\n{}"),
|
|
||||||
value: []byte("{}"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "too long content length",
|
|
||||||
input: []byte("Content-Length: 100\r\n\r\n{}"),
|
|
||||||
err: "lsp: read content: unexpected EOF",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing content length",
|
|
||||||
input: []byte("Content-Length: \r\n\r\n{}"),
|
|
||||||
err: "lsp: invalid content length: parse error: strconv.ParseInt: parsing \"\": invalid syntax",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid header",
|
|
||||||
input: []byte("Nope\r\n\r\n{}"),
|
|
||||||
err: "lsp: invalid header: \"Nope\\r\\n\"",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
r := lsproto.NewBaseReader(bytes.NewReader(tt.input))
|
|
||||||
|
|
||||||
out, err := r.Read()
|
|
||||||
if tt.err != "" {
|
|
||||||
assert.Error(t, err, tt.err)
|
|
||||||
}
|
|
||||||
assert.DeepEqual(t, out, tt.value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseReaderMultipleReads(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
data := []byte(
|
|
||||||
"Content-Length: 4\r\n\r\n1234" +
|
|
||||||
"Content-Length: 2\r\n\r\n{}",
|
|
||||||
)
|
|
||||||
r := lsproto.NewBaseReader(bytes.NewReader(data))
|
|
||||||
|
|
||||||
v1, err := r.Read()
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.DeepEqual(t, v1, []byte("1234"))
|
|
||||||
|
|
||||||
v2, err := r.Read()
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.DeepEqual(t, v2, []byte("{}"))
|
|
||||||
|
|
||||||
_, err = r.Read()
|
|
||||||
assert.Error(t, err, "EOF")
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorReader struct{}
|
|
||||||
|
|
||||||
func (*errorReader) Read([]byte) (int, error) {
|
|
||||||
return 0, errors.New("test error")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseWriter(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
value []byte
|
|
||||||
input []byte
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
value: []byte("{}"),
|
|
||||||
input: []byte("Content-Length: 2\r\n\r\n{}"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bigger object",
|
|
||||||
value: []byte("{\"key\":\"value\"}"),
|
|
||||||
input: []byte("Content-Length: 15\r\n\r\n{\"key\":\"value\"}"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
var b bytes.Buffer
|
|
||||||
w := lsproto.NewBaseWriter(&b)
|
|
||||||
err := w.Write(tt.value)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.DeepEqual(t, b.Bytes(), tt.input)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseWriterWriteError(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
w := lsproto.NewBaseWriter(&errorWriter{})
|
|
||||||
err := w.Write([]byte("{}"))
|
|
||||||
assert.Error(t, err, "test error")
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorWriter struct{}
|
|
||||||
|
|
||||||
func (*errorWriter) Write([]byte) (int, error) {
|
|
||||||
return 0, errors.New("test error")
|
|
||||||
}
|
|
||||||
@ -1,236 +0,0 @@
|
|||||||
package lsproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-json-experiment/json"
|
|
||||||
"github.com/go-json-experiment/json/jsontext"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONRPCVersion struct{}
|
|
||||||
|
|
||||||
const jsonRPCVersion = `"2.0"`
|
|
||||||
|
|
||||||
func (JSONRPCVersion) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(jsonRPCVersion), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrInvalidJSONRPCVersion = errors.New("invalid JSON-RPC version")
|
|
||||||
|
|
||||||
func (*JSONRPCVersion) UnmarshalJSON(data []byte) error {
|
|
||||||
if string(data) != jsonRPCVersion {
|
|
||||||
return ErrInvalidJSONRPCVersion
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ID struct {
|
|
||||||
str string
|
|
||||||
int int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewID(rawValue IntegerOrString) *ID {
|
|
||||||
if rawValue.String != nil {
|
|
||||||
return &ID{str: *rawValue.String}
|
|
||||||
}
|
|
||||||
return &ID{int: *rawValue.Integer}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIDString(str string) *ID {
|
|
||||||
return &ID{str: str}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id *ID) String() string {
|
|
||||||
if id.str != "" {
|
|
||||||
return id.str
|
|
||||||
}
|
|
||||||
return strconv.Itoa(int(id.int))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id *ID) MarshalJSON() ([]byte, error) {
|
|
||||||
if id.str != "" {
|
|
||||||
return json.Marshal(id.str)
|
|
||||||
}
|
|
||||||
return json.Marshal(id.int)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id *ID) UnmarshalJSON(data []byte) error {
|
|
||||||
*id = ID{}
|
|
||||||
if len(data) > 0 && data[0] == '"' {
|
|
||||||
return json.Unmarshal(data, &id.str)
|
|
||||||
}
|
|
||||||
return json.Unmarshal(data, &id.int)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id *ID) TryInt() (int32, bool) {
|
|
||||||
if id == nil || id.str != "" {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return id.int, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id *ID) MustInt() int32 {
|
|
||||||
if id.str != "" {
|
|
||||||
panic("ID is not an integer")
|
|
||||||
}
|
|
||||||
return id.int
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageKind int
|
|
||||||
|
|
||||||
const (
|
|
||||||
MessageKindNotification MessageKind = iota
|
|
||||||
MessageKindRequest
|
|
||||||
MessageKindResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Kind MessageKind
|
|
||||||
msg any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) AsRequest() *RequestMessage {
|
|
||||||
return m.msg.(*RequestMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) AsResponse() *ResponseMessage {
|
|
||||||
return m.msg.(*ResponseMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) UnmarshalJSON(data []byte) error {
|
|
||||||
var raw struct {
|
|
||||||
JSONRPC JSONRPCVersion `json:"jsonrpc"`
|
|
||||||
Method Method `json:"method"`
|
|
||||||
ID *ID `json:"id,omitzero"`
|
|
||||||
Params jsontext.Value `json:"params"`
|
|
||||||
Result any `json:"result,omitzero"`
|
|
||||||
Error *ResponseError `json:"error,omitzero"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
|
||||||
}
|
|
||||||
if raw.ID != nil && raw.Method == "" {
|
|
||||||
m.Kind = MessageKindResponse
|
|
||||||
m.msg = &ResponseMessage{
|
|
||||||
JSONRPC: raw.JSONRPC,
|
|
||||||
ID: raw.ID,
|
|
||||||
Result: raw.Result,
|
|
||||||
Error: raw.Error,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var params any
|
|
||||||
var err error
|
|
||||||
if len(raw.Params) > 0 {
|
|
||||||
params, err = unmarshalParams(raw.Method, raw.Params)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw.ID == nil {
|
|
||||||
m.Kind = MessageKindNotification
|
|
||||||
} else {
|
|
||||||
m.Kind = MessageKindRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
m.msg = &RequestMessage{
|
|
||||||
JSONRPC: raw.JSONRPC,
|
|
||||||
ID: raw.ID,
|
|
||||||
Method: raw.Method,
|
|
||||||
Params: params,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(m.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNotificationMessage(method Method, params any) *RequestMessage {
|
|
||||||
return &RequestMessage{
|
|
||||||
JSONRPC: JSONRPCVersion{},
|
|
||||||
Method: method,
|
|
||||||
Params: params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestMessage struct {
|
|
||||||
JSONRPC JSONRPCVersion `json:"jsonrpc"`
|
|
||||||
ID *ID `json:"id,omitzero"`
|
|
||||||
Method Method `json:"method"`
|
|
||||||
Params any `json:"params,omitzero"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestMessage(method Method, id *ID, params any) *RequestMessage {
|
|
||||||
return &RequestMessage{
|
|
||||||
ID: id,
|
|
||||||
Method: method,
|
|
||||||
Params: params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestMessage) Message() *Message {
|
|
||||||
return &Message{
|
|
||||||
Kind: MessageKindRequest,
|
|
||||||
msg: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestMessage) UnmarshalJSON(data []byte) error {
|
|
||||||
var raw struct {
|
|
||||||
JSONRPC JSONRPCVersion `json:"jsonrpc"`
|
|
||||||
ID *ID `json:"id"`
|
|
||||||
Method Method `json:"method"`
|
|
||||||
Params jsontext.Value `json:"params"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ID = raw.ID
|
|
||||||
r.Method = raw.Method
|
|
||||||
|
|
||||||
var err error
|
|
||||||
r.Params, err = unmarshalParams(raw.Method, raw.Params)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseMessage struct {
|
|
||||||
JSONRPC JSONRPCVersion `json:"jsonrpc"`
|
|
||||||
ID *ID `json:"id,omitzero"`
|
|
||||||
Result any `json:"result,omitzero"`
|
|
||||||
Error *ResponseError `json:"error,omitzero"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResponseMessage) Message() *Message {
|
|
||||||
return &Message{
|
|
||||||
Kind: MessageKindResponse,
|
|
||||||
msg: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseError struct {
|
|
||||||
Code int32 `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data any `json:"data,omitzero"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResponseError) String() string {
|
|
||||||
if r == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(r.Data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("[%d]: %s\n%v", r.Code, r.Message, data)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[%d]: %s", r.Code, r.Message)
|
|
||||||
}
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
package lsproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
||||||
"github.com/go-json-experiment/json"
|
|
||||||
"github.com/go-json-experiment/json/jsontext"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DocumentUri string // !!!
|
|
||||||
|
|
||||||
func (uri DocumentUri) FileName() string {
|
|
||||||
if strings.HasPrefix(string(uri), "file://") {
|
|
||||||
parsed := core.Must(url.Parse(string(uri)))
|
|
||||||
if parsed.Host != "" {
|
|
||||||
return "//" + parsed.Host + parsed.Path
|
|
||||||
}
|
|
||||||
return fixWindowsURIPath(parsed.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leave all other URIs escaped so we can round-trip them.
|
|
||||||
|
|
||||||
scheme, path, ok := strings.Cut(string(uri), ":")
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("invalid URI: %s", uri))
|
|
||||||
}
|
|
||||||
|
|
||||||
authority := "ts-nul-authority"
|
|
||||||
if rest, ok := strings.CutPrefix(path, "//"); ok {
|
|
||||||
authority, path, ok = strings.Cut(rest, "/")
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("invalid URI: %s", uri))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "^/" + scheme + "/" + authority + "/" + path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uri DocumentUri) Path(useCaseSensitiveFileNames bool) tspath.Path {
|
|
||||||
fileName := uri.FileName()
|
|
||||||
return tspath.ToPath(fileName, "", useCaseSensitiveFileNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixWindowsURIPath(path string) string {
|
|
||||||
if rest, ok := strings.CutPrefix(path, "/"); ok {
|
|
||||||
if volume, rest, ok := tspath.SplitVolumePath(rest); ok {
|
|
||||||
return volume + rest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
type HasTextDocumentURI interface {
|
|
||||||
TextDocumentURI() DocumentUri
|
|
||||||
}
|
|
||||||
|
|
||||||
type URI string // !!!
|
|
||||||
|
|
||||||
type Method string
|
|
||||||
|
|
||||||
func unmarshalPtrTo[T any](data []byte) (*T, error) {
|
|
||||||
var v T
|
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal %T: %w", (*T)(nil), err)
|
|
||||||
}
|
|
||||||
return &v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalAny(data []byte) (any, error) {
|
|
||||||
var v any
|
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal any: %w", err)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalEmpty(data []byte) (any, error) {
|
|
||||||
if len(data) != 0 {
|
|
||||||
return nil, fmt.Errorf("expected empty, got: %s", string(data))
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertOnlyOne(message string, values ...bool) {
|
|
||||||
count := 0
|
|
||||||
for _, v := range values {
|
|
||||||
if v {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count != 1 {
|
|
||||||
panic(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAtMostOne(message string, values ...bool) {
|
|
||||||
count := 0
|
|
||||||
for _, v := range values {
|
|
||||||
if v {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 1 {
|
|
||||||
panic(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptrTo[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
type requiredProp bool
|
|
||||||
|
|
||||||
func (v *requiredProp) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
|
||||||
*v = true
|
|
||||||
return dec.SkipValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspired by https://www.youtube.com/watch?v=dab3I-HcTVk
|
|
||||||
|
|
||||||
type RequestInfo[Params, Resp any] struct {
|
|
||||||
_ [0]Params
|
|
||||||
_ [0]Resp
|
|
||||||
Method Method
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotificationInfo[Params any] struct {
|
|
||||||
_ [0]Params
|
|
||||||
Method Method
|
|
||||||
}
|
|
||||||
|
|
||||||
type Null struct{}
|
|
||||||
|
|
||||||
func (Null) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
|
||||||
data, err := dec.ReadValue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if string(data) != "null" {
|
|
||||||
return fmt.Errorf("expected null, got %s", data)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Null) MarshalJSONTo(enc *jsontext.Encoder) error {
|
|
||||||
return enc.WriteToken(jsontext.Null)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,84 +0,0 @@
|
|||||||
package lsproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-json-experiment/json"
|
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnmarshalCompletionItem(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const message = `{
|
|
||||||
"label": "pageXOffset",
|
|
||||||
"insertTextFormat": 1,
|
|
||||||
"textEdit": {
|
|
||||||
"newText": "pageXOffset",
|
|
||||||
"insert": {
|
|
||||||
"start": {
|
|
||||||
"line": 4,
|
|
||||||
"character": 0
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 4,
|
|
||||||
"character": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"replace": {
|
|
||||||
"start": {
|
|
||||||
"line": 4,
|
|
||||||
"character": 0
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 4,
|
|
||||||
"character": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kind": 6,
|
|
||||||
"sortText": "15",
|
|
||||||
"commitCharacters": [
|
|
||||||
".",
|
|
||||||
",",
|
|
||||||
";"
|
|
||||||
]
|
|
||||||
}`
|
|
||||||
|
|
||||||
var result CompletionItem
|
|
||||||
err := json.Unmarshal([]byte(message), &result)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
assert.DeepEqual(t, result, CompletionItem{
|
|
||||||
Label: "pageXOffset",
|
|
||||||
InsertTextFormat: ptrTo(InsertTextFormatPlainText),
|
|
||||||
TextEdit: &TextEditOrInsertReplaceEdit{
|
|
||||||
InsertReplaceEdit: &InsertReplaceEdit{
|
|
||||||
NewText: "pageXOffset",
|
|
||||||
Insert: Range{
|
|
||||||
Start: Position{
|
|
||||||
Line: 4,
|
|
||||||
Character: 0,
|
|
||||||
},
|
|
||||||
End: Position{
|
|
||||||
Line: 4,
|
|
||||||
Character: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Replace: Range{
|
|
||||||
Start: Position{
|
|
||||||
Line: 4,
|
|
||||||
Character: 0,
|
|
||||||
},
|
|
||||||
End: Position{
|
|
||||||
Line: 4,
|
|
||||||
Character: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Kind: ptrTo(CompletionItemKindVariable),
|
|
||||||
SortText: ptrTo("15"),
|
|
||||||
CommitCharacters: ptrTo([]string{".", ",", ";"}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,923 +0,0 @@
|
|||||||
package lsp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"runtime/debug"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ls"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/ata"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/logging"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
|
||||||
"github.com/go-json-experiment/json"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServerOptions struct {
|
|
||||||
In Reader
|
|
||||||
Out Writer
|
|
||||||
Err io.Writer
|
|
||||||
|
|
||||||
Cwd string
|
|
||||||
FS vfs.FS
|
|
||||||
DefaultLibraryPath string
|
|
||||||
TypingsLocation string
|
|
||||||
ParseCache *project.ParseCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(opts *ServerOptions) *Server {
|
|
||||||
if opts.Cwd == "" {
|
|
||||||
panic("Cwd is required")
|
|
||||||
}
|
|
||||||
return &Server{
|
|
||||||
r: opts.In,
|
|
||||||
w: opts.Out,
|
|
||||||
stderr: opts.Err,
|
|
||||||
logger: logging.NewLogger(opts.Err),
|
|
||||||
requestQueue: make(chan *lsproto.RequestMessage, 100),
|
|
||||||
outgoingQueue: make(chan *lsproto.Message, 100),
|
|
||||||
pendingClientRequests: make(map[lsproto.ID]pendingClientRequest),
|
|
||||||
pendingServerRequests: make(map[lsproto.ID]chan *lsproto.ResponseMessage),
|
|
||||||
cwd: opts.Cwd,
|
|
||||||
fs: opts.FS,
|
|
||||||
defaultLibraryPath: opts.DefaultLibraryPath,
|
|
||||||
typingsLocation: opts.TypingsLocation,
|
|
||||||
parseCache: opts.ParseCache,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ ata.NpmExecutor = (*Server)(nil)
|
|
||||||
_ project.Client = (*Server)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type pendingClientRequest struct {
|
|
||||||
req *lsproto.RequestMessage
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
type Reader interface {
|
|
||||||
Read() (*lsproto.Message, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Writer interface {
|
|
||||||
Write(msg *lsproto.Message) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type lspReader struct {
|
|
||||||
r *lsproto.BaseReader
|
|
||||||
}
|
|
||||||
|
|
||||||
type lspWriter struct {
|
|
||||||
w *lsproto.BaseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lspReader) Read() (*lsproto.Message, error) {
|
|
||||||
data, err := r.r.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &lsproto.Message{}
|
|
||||||
if err := json.Unmarshal(data, req); err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %w", lsproto.ErrInvalidRequest, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToReader(r io.Reader) Reader {
|
|
||||||
return &lspReader{r: lsproto.NewBaseReader(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *lspWriter) Write(msg *lsproto.Message) error {
|
|
||||||
data, err := json.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshal message: %w", err)
|
|
||||||
}
|
|
||||||
return w.w.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToWriter(w io.Writer) Writer {
|
|
||||||
return &lspWriter{w: lsproto.NewBaseWriter(w)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ Reader = (*lspReader)(nil)
|
|
||||||
_ Writer = (*lspWriter)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
r Reader
|
|
||||||
w Writer
|
|
||||||
|
|
||||||
stderr io.Writer
|
|
||||||
|
|
||||||
logger logging.Logger
|
|
||||||
clientSeq atomic.Int32
|
|
||||||
requestQueue chan *lsproto.RequestMessage
|
|
||||||
outgoingQueue chan *lsproto.Message
|
|
||||||
pendingClientRequests map[lsproto.ID]pendingClientRequest
|
|
||||||
pendingClientRequestsMu sync.Mutex
|
|
||||||
pendingServerRequests map[lsproto.ID]chan *lsproto.ResponseMessage
|
|
||||||
pendingServerRequestsMu sync.Mutex
|
|
||||||
|
|
||||||
cwd string
|
|
||||||
fs vfs.FS
|
|
||||||
defaultLibraryPath string
|
|
||||||
typingsLocation string
|
|
||||||
|
|
||||||
initializeParams *lsproto.InitializeParams
|
|
||||||
positionEncoding lsproto.PositionEncodingKind
|
|
||||||
locale language.Tag
|
|
||||||
|
|
||||||
watchEnabled bool
|
|
||||||
watcherID atomic.Uint32
|
|
||||||
watchers collections.SyncSet[project.WatcherID]
|
|
||||||
|
|
||||||
session *project.Session
|
|
||||||
|
|
||||||
// !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support
|
|
||||||
compilerOptionsForInferredProjects *core.CompilerOptions
|
|
||||||
// parseCache can be passed in so separate tests can share ASTs
|
|
||||||
parseCache *project.ParseCache
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchFiles implements project.Client.
|
|
||||||
func (s *Server) WatchFiles(ctx context.Context, id project.WatcherID, watchers []*lsproto.FileSystemWatcher) error {
|
|
||||||
_, err := s.sendRequest(ctx, lsproto.MethodClientRegisterCapability, &lsproto.RegistrationParams{
|
|
||||||
Registrations: []*lsproto.Registration{
|
|
||||||
{
|
|
||||||
Id: string(id),
|
|
||||||
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
|
|
||||||
RegisterOptions: ptrTo(any(lsproto.DidChangeWatchedFilesRegistrationOptions{
|
|
||||||
Watchers: watchers,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to register file watcher: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.watchers.Add(id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnwatchFiles implements project.Client.
|
|
||||||
func (s *Server) UnwatchFiles(ctx context.Context, id project.WatcherID) error {
|
|
||||||
if s.watchers.Has(id) {
|
|
||||||
_, err := s.sendRequest(ctx, lsproto.MethodClientUnregisterCapability, &lsproto.UnregistrationParams{
|
|
||||||
Unregisterations: []*lsproto.Unregistration{
|
|
||||||
{
|
|
||||||
Id: string(id),
|
|
||||||
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to unregister file watcher: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.watchers.Delete(id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("no file watcher exists with ID %s", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshDiagnostics implements project.Client.
|
|
||||||
func (s *Server) RefreshDiagnostics(ctx context.Context) error {
|
|
||||||
if s.initializeParams.Capabilities == nil ||
|
|
||||||
s.initializeParams.Capabilities.Workspace == nil ||
|
|
||||||
s.initializeParams.Capabilities.Workspace.Diagnostics == nil ||
|
|
||||||
!ptrIsTrue(s.initializeParams.Capabilities.Workspace.Diagnostics.RefreshSupport) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.sendRequest(ctx, lsproto.MethodWorkspaceDiagnosticRefresh, nil); err != nil {
|
|
||||||
return fmt.Errorf("failed to refresh diagnostics: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run() error {
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
|
||||||
g.Go(func() error { return s.dispatchLoop(ctx) })
|
|
||||||
g.Go(func() error { return s.writeLoop(ctx) })
|
|
||||||
|
|
||||||
// Don't run readLoop in the group, as it blocks on stdin read and cannot be cancelled.
|
|
||||||
readLoopErr := make(chan error, 1)
|
|
||||||
g.Go(func() error {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case err := <-readLoopErr:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
go func() { readLoopErr <- s.readLoop(ctx) }()
|
|
||||||
|
|
||||||
if err := g.Wait(); err != nil && !errors.Is(err, io.EOF) && ctx.Err() != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) readLoop(ctx context.Context) error {
|
|
||||||
for {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
msg, err := s.read()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, lsproto.ErrInvalidRequest) {
|
|
||||||
s.sendError(nil, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.initializeParams == nil && msg.Kind == lsproto.MessageKindRequest {
|
|
||||||
req := msg.AsRequest()
|
|
||||||
if req.Method == lsproto.MethodInitialize {
|
|
||||||
resp, err := s.handleInitialize(ctx, req.Params.(*lsproto.InitializeParams), req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.sendResult(req.ID, resp)
|
|
||||||
} else {
|
|
||||||
s.sendError(req.ID, lsproto.ErrServerNotInitialized)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Kind == lsproto.MessageKindResponse {
|
|
||||||
resp := msg.AsResponse()
|
|
||||||
s.pendingServerRequestsMu.Lock()
|
|
||||||
if respChan, ok := s.pendingServerRequests[*resp.ID]; ok {
|
|
||||||
respChan <- resp
|
|
||||||
close(respChan)
|
|
||||||
delete(s.pendingServerRequests, *resp.ID)
|
|
||||||
}
|
|
||||||
s.pendingServerRequestsMu.Unlock()
|
|
||||||
} else {
|
|
||||||
req := msg.AsRequest()
|
|
||||||
if req.Method == lsproto.MethodCancelRequest {
|
|
||||||
s.cancelRequest(req.Params.(*lsproto.CancelParams).Id)
|
|
||||||
} else {
|
|
||||||
s.requestQueue <- req
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) cancelRequest(rawID lsproto.IntegerOrString) {
|
|
||||||
id := lsproto.NewID(rawID)
|
|
||||||
s.pendingClientRequestsMu.Lock()
|
|
||||||
defer s.pendingClientRequestsMu.Unlock()
|
|
||||||
if pendingReq, ok := s.pendingClientRequests[*id]; ok {
|
|
||||||
pendingReq.cancel()
|
|
||||||
delete(s.pendingClientRequests, *id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) read() (*lsproto.Message, error) {
|
|
||||||
return s.r.Read()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) dispatchLoop(ctx context.Context) error {
|
|
||||||
ctx, lspExit := context.WithCancel(ctx)
|
|
||||||
defer lspExit()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case req := <-s.requestQueue:
|
|
||||||
requestCtx := core.WithLocale(ctx, s.locale)
|
|
||||||
if req.ID != nil {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
requestCtx, cancel = context.WithCancel(core.WithRequestID(requestCtx, req.ID.String()))
|
|
||||||
s.pendingClientRequestsMu.Lock()
|
|
||||||
s.pendingClientRequests[*req.ID] = pendingClientRequest{
|
|
||||||
req: req,
|
|
||||||
cancel: cancel,
|
|
||||||
}
|
|
||||||
s.pendingClientRequestsMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
handle := func() {
|
|
||||||
if err := s.handleRequestOrNotification(requestCtx, req); err != nil {
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
s.sendError(req.ID, lsproto.ErrRequestCancelled)
|
|
||||||
} else if errors.Is(err, io.EOF) {
|
|
||||||
lspExit()
|
|
||||||
} else {
|
|
||||||
s.sendError(req.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.ID != nil {
|
|
||||||
s.pendingClientRequestsMu.Lock()
|
|
||||||
delete(s.pendingClientRequests, *req.ID)
|
|
||||||
s.pendingClientRequestsMu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isBlockingMethod(req.Method) {
|
|
||||||
handle()
|
|
||||||
} else {
|
|
||||||
go handle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) writeLoop(ctx context.Context) error {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case msg := <-s.outgoingQueue:
|
|
||||||
if err := s.w.Write(msg); err != nil {
|
|
||||||
return fmt.Errorf("failed to write message: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) sendRequest(ctx context.Context, method lsproto.Method, params any) (any, error) {
|
|
||||||
id := lsproto.NewIDString(fmt.Sprintf("ts%d", s.clientSeq.Add(1)))
|
|
||||||
req := lsproto.NewRequestMessage(method, id, params)
|
|
||||||
|
|
||||||
responseChan := make(chan *lsproto.ResponseMessage, 1)
|
|
||||||
s.pendingServerRequestsMu.Lock()
|
|
||||||
s.pendingServerRequests[*id] = responseChan
|
|
||||||
s.pendingServerRequestsMu.Unlock()
|
|
||||||
|
|
||||||
s.outgoingQueue <- req.Message()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.pendingServerRequestsMu.Lock()
|
|
||||||
defer s.pendingServerRequestsMu.Unlock()
|
|
||||||
if respChan, ok := s.pendingServerRequests[*id]; ok {
|
|
||||||
close(respChan)
|
|
||||||
delete(s.pendingServerRequests, *id)
|
|
||||||
}
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case resp := <-responseChan:
|
|
||||||
if resp.Error != nil {
|
|
||||||
return nil, fmt.Errorf("request failed: %s", resp.Error.String())
|
|
||||||
}
|
|
||||||
return resp.Result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) sendResult(id *lsproto.ID, result any) {
|
|
||||||
s.sendResponse(&lsproto.ResponseMessage{
|
|
||||||
ID: id,
|
|
||||||
Result: result,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) sendError(id *lsproto.ID, err error) {
|
|
||||||
code := lsproto.ErrInternalError.Code
|
|
||||||
if errCode := (*lsproto.ErrorCode)(nil); errors.As(err, &errCode) {
|
|
||||||
code = errCode.Code
|
|
||||||
}
|
|
||||||
// TODO(jakebailey): error data
|
|
||||||
s.sendResponse(&lsproto.ResponseMessage{
|
|
||||||
ID: id,
|
|
||||||
Error: &lsproto.ResponseError{
|
|
||||||
Code: code,
|
|
||||||
Message: err.Error(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) sendResponse(resp *lsproto.ResponseMessage) {
|
|
||||||
s.outgoingQueue <- resp.Message()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.RequestMessage) error {
|
|
||||||
if handler := handlers()[req.Method]; handler != nil {
|
|
||||||
return handler(s, ctx, req)
|
|
||||||
}
|
|
||||||
s.Log("unknown method", req.Method)
|
|
||||||
if req.ID != nil {
|
|
||||||
s.sendError(req.ID, lsproto.ErrInvalidRequest)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type handlerMap map[lsproto.Method]func(*Server, context.Context, *lsproto.RequestMessage) error
|
|
||||||
|
|
||||||
var handlers = sync.OnceValue(func() handlerMap {
|
|
||||||
handlers := make(handlerMap)
|
|
||||||
|
|
||||||
registerRequestHandler(handlers, lsproto.InitializeInfo, (*Server).handleInitialize)
|
|
||||||
registerNotificationHandler(handlers, lsproto.InitializedInfo, (*Server).handleInitialized)
|
|
||||||
registerRequestHandler(handlers, lsproto.ShutdownInfo, (*Server).handleShutdown)
|
|
||||||
registerNotificationHandler(handlers, lsproto.ExitInfo, (*Server).handleExit)
|
|
||||||
|
|
||||||
registerNotificationHandler(handlers, lsproto.TextDocumentDidOpenInfo, (*Server).handleDidOpen)
|
|
||||||
registerNotificationHandler(handlers, lsproto.TextDocumentDidChangeInfo, (*Server).handleDidChange)
|
|
||||||
registerNotificationHandler(handlers, lsproto.TextDocumentDidSaveInfo, (*Server).handleDidSave)
|
|
||||||
registerNotificationHandler(handlers, lsproto.TextDocumentDidCloseInfo, (*Server).handleDidClose)
|
|
||||||
registerNotificationHandler(handlers, lsproto.WorkspaceDidChangeWatchedFilesInfo, (*Server).handleDidChangeWatchedFiles)
|
|
||||||
registerNotificationHandler(handlers, lsproto.SetTraceInfo, (*Server).handleSetTrace)
|
|
||||||
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDiagnosticInfo, (*Server).handleDocumentDiagnostic)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentHoverInfo, (*Server).handleHover)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDefinitionInfo, (*Server).handleDefinition)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentTypeDefinitionInfo, (*Server).handleTypeDefinition)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCompletionInfo, (*Server).handleCompletion)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentReferencesInfo, (*Server).handleReferences)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentImplementationInfo, (*Server).handleImplementations)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSignatureHelpInfo, (*Server).handleSignatureHelp)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentFormattingInfo, (*Server).handleDocumentFormat)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentRangeFormattingInfo, (*Server).handleDocumentRangeFormat)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentOnTypeFormattingInfo, (*Server).handleDocumentOnTypeFormat)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentSymbolInfo, (*Server).handleDocumentSymbol)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentRenameInfo, (*Server).handleRename)
|
|
||||||
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentHighlightInfo, (*Server).handleDocumentHighlight)
|
|
||||||
registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol)
|
|
||||||
registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve)
|
|
||||||
|
|
||||||
return handlers
|
|
||||||
})
|
|
||||||
|
|
||||||
func registerNotificationHandler[Req any](handlers handlerMap, info lsproto.NotificationInfo[Req], fn func(*Server, context.Context, Req) error) {
|
|
||||||
handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
|
|
||||||
if s.session == nil && req.Method != lsproto.MethodInitialized {
|
|
||||||
return lsproto.ErrServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
var params Req
|
|
||||||
// Ignore empty params; all generated params are either pointers or any.
|
|
||||||
if req.Params != nil {
|
|
||||||
params = req.Params.(Req)
|
|
||||||
}
|
|
||||||
if err := fn(s, ctx, params); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerRequestHandler[Req, Resp any](
|
|
||||||
handlers handlerMap,
|
|
||||||
info lsproto.RequestInfo[Req, Resp],
|
|
||||||
fn func(*Server, context.Context, Req, *lsproto.RequestMessage) (Resp, error),
|
|
||||||
) {
|
|
||||||
handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
|
|
||||||
if s.session == nil && req.Method != lsproto.MethodInitialize {
|
|
||||||
return lsproto.ErrServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
var params Req
|
|
||||||
// Ignore empty params.
|
|
||||||
if req.Params != nil {
|
|
||||||
params = req.Params.(Req)
|
|
||||||
}
|
|
||||||
resp, err := fn(s, ctx, params, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
s.sendResult(req.ID, resp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerLanguageServiceDocumentRequestHandler[Req lsproto.HasTextDocumentURI, Resp any](handlers handlerMap, info lsproto.RequestInfo[Req, Resp], fn func(*Server, context.Context, *ls.LanguageService, Req) (Resp, error)) {
|
|
||||||
handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
|
|
||||||
var params Req
|
|
||||||
// Ignore empty params.
|
|
||||||
if req.Params != nil {
|
|
||||||
params = req.Params.(Req)
|
|
||||||
}
|
|
||||||
ls, err := s.session.GetLanguageService(ctx, params.TextDocumentURI())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.recover(req)
|
|
||||||
resp, err := fn(s, ctx, ls, params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
s.sendResult(req.ID, resp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) recover(req *lsproto.RequestMessage) {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
stack := debug.Stack()
|
|
||||||
s.Log("panic handling request", req.Method, r, string(stack))
|
|
||||||
if req.ID != nil {
|
|
||||||
s.sendError(req.ID, fmt.Errorf("%w: panic handling request %s: %v", lsproto.ErrInternalError, req.Method, r))
|
|
||||||
} else {
|
|
||||||
s.Log("unhandled panic in notification", req.Method, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleInitialize(ctx context.Context, params *lsproto.InitializeParams, _ *lsproto.RequestMessage) (lsproto.InitializeResponse, error) {
|
|
||||||
if s.initializeParams != nil {
|
|
||||||
return nil, lsproto.ErrInvalidRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
s.initializeParams = params
|
|
||||||
|
|
||||||
s.positionEncoding = lsproto.PositionEncodingKindUTF16
|
|
||||||
if genCapabilities := s.initializeParams.Capabilities.General; genCapabilities != nil && genCapabilities.PositionEncodings != nil {
|
|
||||||
if slices.Contains(*genCapabilities.PositionEncodings, lsproto.PositionEncodingKindUTF8) {
|
|
||||||
s.positionEncoding = lsproto.PositionEncodingKindUTF8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.initializeParams.Locale != nil {
|
|
||||||
locale, err := language.Parse(*s.initializeParams.Locale)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.locale = locale
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.initializeParams.Trace != nil && *s.initializeParams.Trace == "verbose" {
|
|
||||||
s.logger.SetVerbose(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &lsproto.InitializeResult{
|
|
||||||
ServerInfo: &lsproto.ServerInfo{
|
|
||||||
Name: "typescript-go",
|
|
||||||
Version: ptrTo(core.Version()),
|
|
||||||
},
|
|
||||||
Capabilities: &lsproto.ServerCapabilities{
|
|
||||||
PositionEncoding: ptrTo(s.positionEncoding),
|
|
||||||
TextDocumentSync: &lsproto.TextDocumentSyncOptionsOrKind{
|
|
||||||
Options: &lsproto.TextDocumentSyncOptions{
|
|
||||||
OpenClose: ptrTo(true),
|
|
||||||
Change: ptrTo(lsproto.TextDocumentSyncKindIncremental),
|
|
||||||
Save: &lsproto.BooleanOrSaveOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
HoverProvider: &lsproto.BooleanOrHoverOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
DefinitionProvider: &lsproto.BooleanOrDefinitionOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
TypeDefinitionProvider: &lsproto.BooleanOrTypeDefinitionOptionsOrTypeDefinitionRegistrationOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
ReferencesProvider: &lsproto.BooleanOrReferenceOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
ImplementationProvider: &lsproto.BooleanOrImplementationOptionsOrImplementationRegistrationOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
DiagnosticProvider: &lsproto.DiagnosticOptionsOrRegistrationOptions{
|
|
||||||
Options: &lsproto.DiagnosticOptions{
|
|
||||||
InterFileDependencies: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CompletionProvider: &lsproto.CompletionOptions{
|
|
||||||
TriggerCharacters: &ls.TriggerCharacters,
|
|
||||||
ResolveProvider: ptrTo(true),
|
|
||||||
// !!! other options
|
|
||||||
},
|
|
||||||
SignatureHelpProvider: &lsproto.SignatureHelpOptions{
|
|
||||||
TriggerCharacters: &[]string{"(", ","},
|
|
||||||
},
|
|
||||||
DocumentFormattingProvider: &lsproto.BooleanOrDocumentFormattingOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
DocumentRangeFormattingProvider: &lsproto.BooleanOrDocumentRangeFormattingOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
DocumentOnTypeFormattingProvider: &lsproto.DocumentOnTypeFormattingOptions{
|
|
||||||
FirstTriggerCharacter: "{",
|
|
||||||
MoreTriggerCharacter: &[]string{"}", ";", "\n"},
|
|
||||||
},
|
|
||||||
WorkspaceSymbolProvider: &lsproto.BooleanOrWorkspaceSymbolOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
DocumentSymbolProvider: &lsproto.BooleanOrDocumentSymbolOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
RenameProvider: &lsproto.BooleanOrRenameOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
DocumentHighlightProvider: &lsproto.BooleanOrDocumentHighlightOptions{
|
|
||||||
Boolean: ptrTo(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleInitialized(ctx context.Context, params *lsproto.InitializedParams) error {
|
|
||||||
if shouldEnableWatch(s.initializeParams) {
|
|
||||||
s.watchEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
cwd := s.cwd
|
|
||||||
if s.initializeParams.Capabilities != nil &&
|
|
||||||
s.initializeParams.Capabilities.Workspace != nil &&
|
|
||||||
s.initializeParams.Capabilities.Workspace.WorkspaceFolders != nil &&
|
|
||||||
ptrIsTrue(s.initializeParams.Capabilities.Workspace.WorkspaceFolders) &&
|
|
||||||
s.initializeParams.WorkspaceFolders != nil &&
|
|
||||||
s.initializeParams.WorkspaceFolders.WorkspaceFolders != nil &&
|
|
||||||
len(*s.initializeParams.WorkspaceFolders.WorkspaceFolders) == 1 {
|
|
||||||
cwd = lsproto.DocumentUri((*s.initializeParams.WorkspaceFolders.WorkspaceFolders)[0].Uri).FileName()
|
|
||||||
} else if s.initializeParams.RootUri.DocumentUri != nil {
|
|
||||||
cwd = s.initializeParams.RootUri.DocumentUri.FileName()
|
|
||||||
} else if s.initializeParams.RootPath != nil && s.initializeParams.RootPath.String != nil {
|
|
||||||
cwd = *s.initializeParams.RootPath.String
|
|
||||||
}
|
|
||||||
if !tspath.PathIsAbsolute(cwd) {
|
|
||||||
cwd = s.cwd
|
|
||||||
}
|
|
||||||
|
|
||||||
s.session = project.NewSession(&project.SessionInit{
|
|
||||||
Options: &project.SessionOptions{
|
|
||||||
CurrentDirectory: cwd,
|
|
||||||
DefaultLibraryPath: s.defaultLibraryPath,
|
|
||||||
TypingsLocation: s.typingsLocation,
|
|
||||||
PositionEncoding: s.positionEncoding,
|
|
||||||
WatchEnabled: s.watchEnabled,
|
|
||||||
LoggingEnabled: true,
|
|
||||||
DebounceDelay: 500 * time.Millisecond,
|
|
||||||
},
|
|
||||||
FS: s.fs,
|
|
||||||
Logger: s.logger,
|
|
||||||
Client: s,
|
|
||||||
NpmExecutor: s,
|
|
||||||
ParseCache: s.parseCache,
|
|
||||||
})
|
|
||||||
// !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support
|
|
||||||
if s.compilerOptionsForInferredProjects != nil {
|
|
||||||
s.session.DidChangeCompilerOptionsForInferredProjects(ctx, s.compilerOptionsForInferredProjects)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleShutdown(ctx context.Context, params any, _ *lsproto.RequestMessage) (lsproto.ShutdownResponse, error) {
|
|
||||||
s.session.Close()
|
|
||||||
return lsproto.ShutdownResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleExit(ctx context.Context, params any) error {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDidOpen(ctx context.Context, params *lsproto.DidOpenTextDocumentParams) error {
|
|
||||||
s.session.DidOpenFile(ctx, params.TextDocument.Uri, params.TextDocument.Version, params.TextDocument.Text, params.TextDocument.LanguageId)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDidChange(ctx context.Context, params *lsproto.DidChangeTextDocumentParams) error {
|
|
||||||
s.session.DidChangeFile(ctx, params.TextDocument.Uri, params.TextDocument.Version, params.ContentChanges)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDidSave(ctx context.Context, params *lsproto.DidSaveTextDocumentParams) error {
|
|
||||||
s.session.DidSaveFile(ctx, params.TextDocument.Uri)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDidClose(ctx context.Context, params *lsproto.DidCloseTextDocumentParams) error {
|
|
||||||
s.session.DidCloseFile(ctx, params.TextDocument.Uri)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDidChangeWatchedFiles(ctx context.Context, params *lsproto.DidChangeWatchedFilesParams) error {
|
|
||||||
s.session.DidChangeWatchedFiles(ctx, params.Changes)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleSetTrace(ctx context.Context, params *lsproto.SetTraceParams) error {
|
|
||||||
switch params.Value {
|
|
||||||
case "verbose":
|
|
||||||
s.logger.SetVerbose(true)
|
|
||||||
case "messages":
|
|
||||||
s.logger.SetVerbose(false)
|
|
||||||
case "off":
|
|
||||||
// !!! logging cannot be completely turned off for now
|
|
||||||
s.logger.SetVerbose(false)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown trace value: %s", params.Value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDocumentDiagnostic(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentDiagnosticParams) (lsproto.DocumentDiagnosticResponse, error) {
|
|
||||||
return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleHover(ctx context.Context, ls *ls.LanguageService, params *lsproto.HoverParams) (lsproto.HoverResponse, error) {
|
|
||||||
return ls.ProvideHover(ctx, params.TextDocument.Uri, params.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleSignatureHelp(ctx context.Context, languageService *ls.LanguageService, params *lsproto.SignatureHelpParams) (lsproto.SignatureHelpResponse, error) {
|
|
||||||
return languageService.ProvideSignatureHelp(
|
|
||||||
ctx,
|
|
||||||
params.TextDocument.Uri,
|
|
||||||
params.Position,
|
|
||||||
params.Context,
|
|
||||||
s.initializeParams.Capabilities.TextDocument.SignatureHelp,
|
|
||||||
&ls.UserPreferences{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.DefinitionParams) (lsproto.DefinitionResponse, error) {
|
|
||||||
return ls.ProvideDefinition(ctx, params.TextDocument.Uri, params.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleTypeDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.TypeDefinitionParams) (lsproto.TypeDefinitionResponse, error) {
|
|
||||||
return ls.ProvideTypeDefinition(ctx, params.TextDocument.Uri, params.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleReferences(ctx context.Context, ls *ls.LanguageService, params *lsproto.ReferenceParams) (lsproto.ReferencesResponse, error) {
|
|
||||||
// findAllReferences
|
|
||||||
return ls.ProvideReferences(ctx, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleImplementations(ctx context.Context, ls *ls.LanguageService, params *lsproto.ImplementationParams) (lsproto.ImplementationResponse, error) {
|
|
||||||
// goToImplementation
|
|
||||||
return ls.ProvideImplementations(ctx, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleCompletion(ctx context.Context, languageService *ls.LanguageService, params *lsproto.CompletionParams) (lsproto.CompletionResponse, error) {
|
|
||||||
// !!! get user preferences
|
|
||||||
return languageService.ProvideCompletion(
|
|
||||||
ctx,
|
|
||||||
params.TextDocument.Uri,
|
|
||||||
params.Position,
|
|
||||||
params.Context,
|
|
||||||
getCompletionClientCapabilities(s.initializeParams),
|
|
||||||
&ls.UserPreferences{
|
|
||||||
IncludeCompletionsForModuleExports: core.TSTrue,
|
|
||||||
IncludeCompletionsForImportStatements: core.TSTrue,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleCompletionItemResolve(ctx context.Context, params *lsproto.CompletionItem, reqMsg *lsproto.RequestMessage) (lsproto.CompletionResolveResponse, error) {
|
|
||||||
data, err := ls.GetCompletionItemData(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
languageService, err := s.session.GetLanguageService(ctx, ls.FileNameToDocumentURI(data.FileName))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer s.recover(reqMsg)
|
|
||||||
return languageService.ResolveCompletionItem(
|
|
||||||
ctx,
|
|
||||||
params,
|
|
||||||
data,
|
|
||||||
getCompletionClientCapabilities(s.initializeParams),
|
|
||||||
&ls.UserPreferences{
|
|
||||||
IncludeCompletionsForModuleExports: core.TSTrue,
|
|
||||||
IncludeCompletionsForImportStatements: core.TSTrue,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDocumentFormat(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentFormattingParams) (lsproto.DocumentFormattingResponse, error) {
|
|
||||||
return ls.ProvideFormatDocument(
|
|
||||||
ctx,
|
|
||||||
params.TextDocument.Uri,
|
|
||||||
params.Options,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDocumentRangeFormat(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentRangeFormattingParams) (lsproto.DocumentRangeFormattingResponse, error) {
|
|
||||||
return ls.ProvideFormatDocumentRange(
|
|
||||||
ctx,
|
|
||||||
params.TextDocument.Uri,
|
|
||||||
params.Options,
|
|
||||||
params.Range,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDocumentOnTypeFormat(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentOnTypeFormattingParams) (lsproto.DocumentOnTypeFormattingResponse, error) {
|
|
||||||
return ls.ProvideFormatDocumentOnType(
|
|
||||||
ctx,
|
|
||||||
params.TextDocument.Uri,
|
|
||||||
params.Options,
|
|
||||||
params.Position,
|
|
||||||
params.Ch,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleWorkspaceSymbol(ctx context.Context, params *lsproto.WorkspaceSymbolParams, reqMsg *lsproto.RequestMessage) (lsproto.WorkspaceSymbolResponse, error) {
|
|
||||||
snapshot, release := s.session.Snapshot()
|
|
||||||
defer release()
|
|
||||||
defer s.recover(reqMsg)
|
|
||||||
programs := core.Map(snapshot.ProjectCollection.Projects(), (*project.Project).GetProgram)
|
|
||||||
return ls.ProvideWorkspaceSymbols(ctx, programs, snapshot.Converters(), params.Query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDocumentSymbol(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentSymbolParams) (lsproto.DocumentSymbolResponse, error) {
|
|
||||||
return ls.ProvideDocumentSymbols(ctx, params.TextDocument.Uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleRename(ctx context.Context, ls *ls.LanguageService, params *lsproto.RenameParams) (lsproto.RenameResponse, error) {
|
|
||||||
return ls.ProvideRename(ctx, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleDocumentHighlight(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentHighlightParams) (lsproto.DocumentHighlightResponse, error) {
|
|
||||||
return ls.ProvideDocumentHighlights(ctx, params.TextDocument.Uri, params.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Log(msg ...any) {
|
|
||||||
fmt.Fprintln(s.stderr, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support
|
|
||||||
func (s *Server) SetCompilerOptionsForInferredProjects(ctx context.Context, options *core.CompilerOptions) {
|
|
||||||
s.compilerOptionsForInferredProjects = options
|
|
||||||
if s.session != nil {
|
|
||||||
s.session.DidChangeCompilerOptionsForInferredProjects(ctx, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NpmInstall implements ata.NpmExecutor
|
|
||||||
func (s *Server) NpmInstall(cwd string, args []string) ([]byte, error) {
|
|
||||||
cmd := exec.Command("npm", args...)
|
|
||||||
cmd.Dir = cwd
|
|
||||||
return cmd.Output()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBlockingMethod(method lsproto.Method) bool {
|
|
||||||
switch method {
|
|
||||||
case lsproto.MethodInitialize,
|
|
||||||
lsproto.MethodInitialized,
|
|
||||||
lsproto.MethodTextDocumentDidOpen,
|
|
||||||
lsproto.MethodTextDocumentDidChange,
|
|
||||||
lsproto.MethodTextDocumentDidSave,
|
|
||||||
lsproto.MethodTextDocumentDidClose,
|
|
||||||
lsproto.MethodWorkspaceDidChangeWatchedFiles:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptrTo[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptrIsTrue(v *bool) bool {
|
|
||||||
if v == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldEnableWatch(params *lsproto.InitializeParams) bool {
|
|
||||||
if params == nil || params.Capabilities == nil || params.Capabilities.Workspace == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return params.Capabilities.Workspace.DidChangeWatchedFiles != nil &&
|
|
||||||
ptrIsTrue(params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCompletionClientCapabilities(params *lsproto.InitializeParams) *lsproto.CompletionClientCapabilities {
|
|
||||||
if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return params.Capabilities.TextDocument.Completion
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user