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