mirror of
https://github.com/microsoft/monaco-editor.git
synced 2025-12-22 05:50:11 +01:00
First iteration of monaco editor lsp client (#5044)
This commit is contained in:
parent
a59f6c8a72
commit
0fd6f29a23
42 changed files with 14026 additions and 4 deletions
5
monaco-lsp-client/README.md
Normal file
5
monaco-lsp-client/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Monaco LSP Client
|
||||
|
||||
Provides a Language Server Protocol (LSP) client for the Monaco Editor.
|
||||
|
||||
This package is in alpha stage and might contain many bugs.
|
||||
687
monaco-lsp-client/generator/index.ts
Normal file
687
monaco-lsp-client/generator/index.ts
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Utility class for writing formatted code with proper indentation
|
||||
*/
|
||||
class LineWriter {
|
||||
private lines: string[] = [];
|
||||
private indentLevel: number = 0;
|
||||
private indentStr: string = ' '; // 4 spaces
|
||||
|
||||
/**
|
||||
* Write a line with current indentation
|
||||
*/
|
||||
writeLine(line: string = ''): void {
|
||||
if (line.trim() === '') {
|
||||
this.lines.push('');
|
||||
} else {
|
||||
this.lines.push(this.indentStr.repeat(this.indentLevel) + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write text without adding a new line
|
||||
*/
|
||||
write(text: string): void {
|
||||
if (this.lines.length === 0) {
|
||||
this.lines.push('');
|
||||
}
|
||||
const lastIndex = this.lines.length - 1;
|
||||
if (this.lines[lastIndex] === '') {
|
||||
this.lines[lastIndex] = this.indentStr.repeat(this.indentLevel) + text;
|
||||
} else {
|
||||
this.lines[lastIndex] += text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase indentation level
|
||||
*/
|
||||
indent(): void {
|
||||
this.indentLevel++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease indentation level
|
||||
*/
|
||||
outdent(): void {
|
||||
if (this.indentLevel > 0) {
|
||||
this.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generated content as a string
|
||||
*/
|
||||
toString(): string {
|
||||
return this.lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all content and reset indentation
|
||||
*/
|
||||
clear(): void {
|
||||
this.lines = [];
|
||||
this.indentLevel = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definitions based on the metaModel schema
|
||||
*/
|
||||
interface MetaModel {
|
||||
metaData: MetaData;
|
||||
requests: Request[];
|
||||
notifications: Notification[];
|
||||
structures: Structure[];
|
||||
enumerations: Enumeration[];
|
||||
typeAliases: TypeAlias[];
|
||||
}
|
||||
|
||||
interface MetaData {
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface Request {
|
||||
method: string;
|
||||
result: Type;
|
||||
messageDirection: MessageDirection;
|
||||
params?: Type | Type[];
|
||||
partialResult?: Type;
|
||||
errorData?: Type;
|
||||
registrationOptions?: Type;
|
||||
registrationMethod?: string;
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface Notification {
|
||||
method: string;
|
||||
messageDirection: MessageDirection;
|
||||
params?: Type | Type[];
|
||||
registrationOptions?: Type;
|
||||
registrationMethod?: string;
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface Structure {
|
||||
name: string;
|
||||
properties: Property[];
|
||||
extends?: Type[];
|
||||
mixins?: Type[];
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface Property {
|
||||
name: string;
|
||||
type: Type;
|
||||
optional?: boolean;
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface Enumeration {
|
||||
name: string;
|
||||
type: EnumerationType;
|
||||
values: EnumerationEntry[];
|
||||
supportsCustomValues?: boolean;
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface EnumerationEntry {
|
||||
name: string;
|
||||
value: string | number;
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface EnumerationType {
|
||||
kind: 'base';
|
||||
name: 'string' | 'integer' | 'uinteger';
|
||||
}
|
||||
|
||||
interface TypeAlias {
|
||||
name: string;
|
||||
type: Type;
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
type MessageDirection = 'clientToServer' | 'serverToClient' | 'both';
|
||||
|
||||
type Type =
|
||||
| BaseType
|
||||
| ReferenceType
|
||||
| ArrayType
|
||||
| MapType
|
||||
| AndType
|
||||
| OrType
|
||||
| TupleType
|
||||
| StructureLiteralType
|
||||
| StringLiteralType
|
||||
| IntegerLiteralType
|
||||
| BooleanLiteralType;
|
||||
|
||||
interface BaseType {
|
||||
kind: 'base';
|
||||
name:
|
||||
| 'URI'
|
||||
| 'DocumentUri'
|
||||
| 'integer'
|
||||
| 'uinteger'
|
||||
| 'decimal'
|
||||
| 'RegExp'
|
||||
| 'string'
|
||||
| 'boolean'
|
||||
| 'null';
|
||||
}
|
||||
|
||||
interface ReferenceType {
|
||||
kind: 'reference';
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ArrayType {
|
||||
kind: 'array';
|
||||
element: Type;
|
||||
}
|
||||
|
||||
interface MapType {
|
||||
kind: 'map';
|
||||
key: Type;
|
||||
value: Type;
|
||||
}
|
||||
|
||||
interface AndType {
|
||||
kind: 'and';
|
||||
items: Type[];
|
||||
}
|
||||
|
||||
interface OrType {
|
||||
kind: 'or';
|
||||
items: Type[];
|
||||
}
|
||||
|
||||
interface TupleType {
|
||||
kind: 'tuple';
|
||||
items: Type[];
|
||||
}
|
||||
|
||||
interface StructureLiteralType {
|
||||
kind: 'literal';
|
||||
value: StructureLiteral;
|
||||
}
|
||||
|
||||
interface StructureLiteral {
|
||||
properties: Property[];
|
||||
documentation?: string;
|
||||
since?: string;
|
||||
proposed?: boolean;
|
||||
deprecated?: string;
|
||||
}
|
||||
|
||||
interface StringLiteralType {
|
||||
kind: 'stringLiteral';
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface IntegerLiteralType {
|
||||
kind: 'integerLiteral';
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface BooleanLiteralType {
|
||||
kind: 'booleanLiteral';
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeScript types generator for LSP client
|
||||
*/
|
||||
class LSPTypesGenerator {
|
||||
private writer = new LineWriter();
|
||||
|
||||
/**
|
||||
* Load and parse the metaModel.json file
|
||||
*/
|
||||
private loadMetaModel(): MetaModel {
|
||||
const metaModelPath = path.join(__dirname, '..', 'metaModel.json');
|
||||
const content = fs.readFileSync(metaModelPath, 'utf-8');
|
||||
return JSON.parse(content) as MetaModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Type to TypeScript type string
|
||||
*/
|
||||
private typeToTypeScript(type: Type): string {
|
||||
switch (type.kind) {
|
||||
case 'base':
|
||||
switch (type.name) {
|
||||
case 'string':
|
||||
case 'DocumentUri':
|
||||
case 'URI':
|
||||
return 'string';
|
||||
case 'integer':
|
||||
case 'uinteger':
|
||||
case 'decimal':
|
||||
return 'number';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'null':
|
||||
return 'null';
|
||||
case 'RegExp':
|
||||
return 'RegExp';
|
||||
default:
|
||||
return 'any';
|
||||
}
|
||||
case 'reference':
|
||||
return type.name;
|
||||
case 'array':
|
||||
return `(${this.typeToTypeScript(type.element)})[]`;
|
||||
case 'map':
|
||||
return `{ [key: ${this.typeToTypeScript(type.key)}]: ${this.typeToTypeScript(
|
||||
type.value
|
||||
)} }`;
|
||||
case 'and':
|
||||
return type.items.map((item) => this.typeToTypeScript(item)).join(' & ');
|
||||
case 'or':
|
||||
return type.items.map((item) => this.typeToTypeScript(item)).join(' | ');
|
||||
case 'tuple':
|
||||
return `[${type.items.map((item) => this.typeToTypeScript(item)).join(', ')}]`;
|
||||
case 'literal':
|
||||
return this.structureLiteralToTypeScript(type.value);
|
||||
case 'stringLiteral':
|
||||
return `'${type.value}'`;
|
||||
case 'integerLiteral':
|
||||
return type.value.toString();
|
||||
case 'booleanLiteral':
|
||||
return type.value.toString();
|
||||
default:
|
||||
return 'any';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert structure literal to TypeScript interface
|
||||
*/
|
||||
private structureLiteralToTypeScript(literal: StructureLiteral): string {
|
||||
const properties = literal.properties.map((prop) => {
|
||||
const optional = prop.optional ? '?' : '';
|
||||
return `${prop.name}${optional}: ${this.typeToTypeScript(prop.type)}`;
|
||||
});
|
||||
return `{\n ${properties.join(';\n ')}\n}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate TypeScript interface for a structure
|
||||
*/
|
||||
private generateStructure(structure: Structure): void {
|
||||
if (structure.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${structure.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
|
||||
// Build extends clause combining extends and mixins
|
||||
const allParents: string[] = [];
|
||||
|
||||
if (structure.extends && structure.extends.length > 0) {
|
||||
allParents.push(...structure.extends.map((type) => this.typeToTypeScript(type)));
|
||||
}
|
||||
|
||||
if (structure.mixins && structure.mixins.length > 0) {
|
||||
allParents.push(...structure.mixins.map((type) => this.typeToTypeScript(type)));
|
||||
}
|
||||
|
||||
const extendsClause = allParents.length > 0 ? ` extends ${allParents.join(', ')}` : '';
|
||||
|
||||
this.writer.writeLine(`export interface ${structure.name}${extendsClause} {`);
|
||||
this.writer.indent();
|
||||
|
||||
// Add properties
|
||||
for (const property of structure.properties) {
|
||||
if (property.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${property.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
const optional = property.optional ? '?' : '';
|
||||
this.writer.writeLine(
|
||||
`${property.name}${optional}: ${this.typeToTypeScript(property.type)};`
|
||||
);
|
||||
}
|
||||
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('}');
|
||||
this.writer.writeLine('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate TypeScript enum for an enumeration
|
||||
*/
|
||||
private generateEnumeration(enumeration: Enumeration): void {
|
||||
if (enumeration.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${enumeration.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
|
||||
this.writer.writeLine(`export enum ${enumeration.name} {`);
|
||||
this.writer.indent();
|
||||
|
||||
for (let i = 0; i < enumeration.values.length; i++) {
|
||||
const entry = enumeration.values[i];
|
||||
if (entry.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${entry.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
const isLast = i === enumeration.values.length - 1;
|
||||
const comma = isLast ? '' : ',';
|
||||
if (typeof entry.value === 'string') {
|
||||
this.writer.writeLine(`${entry.name} = '${entry.value}'${comma}`);
|
||||
} else {
|
||||
this.writer.writeLine(`${entry.name} = ${entry.value}${comma}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('}');
|
||||
this.writer.writeLine('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate TypeScript type alias
|
||||
*/
|
||||
private generateTypeAlias(typeAlias: TypeAlias): void {
|
||||
if (typeAlias.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${typeAlias.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
|
||||
this.writer.writeLine(
|
||||
`export type ${typeAlias.name} = ${this.typeToTypeScript(typeAlias.type)};`
|
||||
);
|
||||
this.writer.writeLine('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Capability class
|
||||
*/
|
||||
private generateCapabilityClass(): void {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(
|
||||
' * Represents a capability with its associated method and registration options type'
|
||||
);
|
||||
this.writer.writeLine(' */');
|
||||
this.writer.writeLine('export class Capability<T> {');
|
||||
this.writer.indent();
|
||||
this.writer.writeLine('constructor(public readonly method: string) {}');
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('}');
|
||||
this.writer.writeLine('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the capabilities map
|
||||
*/
|
||||
private generateCapabilitiesMap(metaModel: MetaModel): void {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(' * Map of all LSP capabilities with their registration options');
|
||||
this.writer.writeLine(' */');
|
||||
this.writer.writeLine('export const capabilities = {');
|
||||
this.writer.indent();
|
||||
|
||||
// Collect all requests and notifications with registration options
|
||||
const itemsWithRegistration: Array<{ method: string; registrationOptions?: Type }> = [];
|
||||
|
||||
for (const request of metaModel.requests) {
|
||||
if (request.registrationOptions) {
|
||||
itemsWithRegistration.push({
|
||||
method: request.method,
|
||||
registrationOptions: request.registrationOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const notification of metaModel.notifications) {
|
||||
if (notification.registrationOptions) {
|
||||
itemsWithRegistration.push({
|
||||
method: notification.method,
|
||||
registrationOptions: notification.registrationOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate capability entries
|
||||
for (const item of itemsWithRegistration) {
|
||||
const methodIdentifier = this.methodToIdentifier(item.method);
|
||||
const registrationType = item.registrationOptions
|
||||
? this.typeToTypeScript(item.registrationOptions)
|
||||
: 'unknown';
|
||||
|
||||
this.writer.writeLine(
|
||||
`${methodIdentifier}: new Capability<${registrationType}>('${item.method}'),`
|
||||
);
|
||||
}
|
||||
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('};');
|
||||
this.writer.writeLine('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert LSP method name to valid JavaScript identifier
|
||||
*/
|
||||
private methodToIdentifier(method: string): string {
|
||||
const parts = method
|
||||
.replace(/\$/g, '') // Remove $ characters
|
||||
.split('/') // Split on forward slashes
|
||||
.filter((part) => part.length > 0); // Remove empty parts
|
||||
|
||||
return parts
|
||||
.map((part, index) => {
|
||||
// Convert kebab-case to camelCase for each part
|
||||
const camelCase = part.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
// Capitalize first letter of all parts except the first non-empty part
|
||||
return index === 0 ? camelCase : camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the API contract object
|
||||
*/
|
||||
private generateApiContract(metaModel: MetaModel): void {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(' * LSP API Contract');
|
||||
this.writer.writeLine(' */');
|
||||
|
||||
this.writer.writeLine('export const api = contract({');
|
||||
this.writer.indent();
|
||||
|
||||
this.writer.writeLine('name: "LSP",');
|
||||
|
||||
// Helper function to generate request entries
|
||||
const generateRequest = (request: Request, isOptional: boolean = false) => {
|
||||
const methodIdentifier = this.methodToIdentifier(request.method);
|
||||
const paramsType = this.getParamsType(request.params);
|
||||
const resultType = this.typeToTypeScript(request.result);
|
||||
|
||||
if (request.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${request.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
|
||||
const optional = isOptional ? '.optional()' : '';
|
||||
this.writer.writeLine(
|
||||
`${methodIdentifier}: unverifiedRequest<${paramsType}, ${resultType}>({ method: "${request.method}" })${optional},`
|
||||
);
|
||||
};
|
||||
|
||||
// Helper function to generate notification entries
|
||||
const generateNotification = (notification: Notification) => {
|
||||
const methodIdentifier = this.methodToIdentifier(notification.method);
|
||||
const paramsType = this.getParamsType(notification.params);
|
||||
|
||||
if (notification.documentation) {
|
||||
this.writer.writeLine('/**');
|
||||
this.writer.writeLine(` * ${notification.documentation.replace(/\n/g, '\n * ')}`);
|
||||
this.writer.writeLine(' */');
|
||||
}
|
||||
this.writer.writeLine(
|
||||
`${methodIdentifier}: unverifiedNotification<${paramsType}>({ method: "${notification.method}" }),`
|
||||
);
|
||||
};
|
||||
|
||||
// Server section
|
||||
this.writer.writeLine('server: {');
|
||||
this.writer.indent();
|
||||
|
||||
// Server requests (sent from client to server)
|
||||
for (const request of metaModel.requests) {
|
||||
if (request.messageDirection === 'clientToServer' || request.messageDirection === 'both') {
|
||||
generateRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
// Server notifications (sent from client to server)
|
||||
for (const notification of metaModel.notifications) {
|
||||
if (
|
||||
notification.messageDirection === 'clientToServer' ||
|
||||
notification.messageDirection === 'both'
|
||||
) {
|
||||
generateNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('},');
|
||||
|
||||
// Client section
|
||||
this.writer.writeLine('client: {');
|
||||
this.writer.indent();
|
||||
|
||||
// Client requests (handled by server)
|
||||
for (const request of metaModel.requests) {
|
||||
if (request.messageDirection === 'serverToClient' || request.messageDirection === 'both') {
|
||||
generateRequest(request, true); // serverToClient requests are optional
|
||||
}
|
||||
}
|
||||
|
||||
// Client notifications (sent from server to client)
|
||||
for (const notification of metaModel.notifications) {
|
||||
if (
|
||||
notification.messageDirection === 'serverToClient' ||
|
||||
notification.messageDirection === 'both'
|
||||
) {
|
||||
generateNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('}');
|
||||
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('});');
|
||||
this.writer.writeLine('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get parameter type
|
||||
*/
|
||||
private getParamsType(params?: Type | Type[]): string {
|
||||
if (!params) {
|
||||
return 'void';
|
||||
}
|
||||
if (Array.isArray(params)) {
|
||||
const paramTypes = params.map((p) => this.typeToTypeScript(p));
|
||||
return `[${paramTypes.join(', ')}]`;
|
||||
} else {
|
||||
return this.typeToTypeScript(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the complete TypeScript types
|
||||
*/
|
||||
generate(): void {
|
||||
const metaModel = this.loadMetaModel();
|
||||
|
||||
this.writer.clear();
|
||||
this.writer.writeLine('// Generated TypeScript definitions for LSP');
|
||||
this.writer.writeLine(`// Protocol version: ${metaModel.metaData.version}`);
|
||||
this.writer.writeLine('// This file is auto-generated. Do not edit manually.');
|
||||
this.writer.writeLine('');
|
||||
|
||||
// Import contract types from @hediet/json-rpc
|
||||
this.writer.writeLine('import {');
|
||||
this.writer.indent();
|
||||
this.writer.writeLine('contract,');
|
||||
this.writer.writeLine('Contract,');
|
||||
this.writer.writeLine('unverifiedRequest,');
|
||||
this.writer.writeLine('unverifiedNotification,');
|
||||
this.writer.outdent();
|
||||
this.writer.writeLine('} from "@hediet/json-rpc";');
|
||||
this.writer.writeLine('');
|
||||
|
||||
// Generate enumerations
|
||||
for (const enumeration of metaModel.enumerations) {
|
||||
this.generateEnumeration(enumeration);
|
||||
}
|
||||
|
||||
// Generate type aliases
|
||||
for (const typeAlias of metaModel.typeAliases) {
|
||||
this.generateTypeAlias(typeAlias);
|
||||
}
|
||||
|
||||
// Generate structures
|
||||
for (const structure of metaModel.structures) {
|
||||
this.generateStructure(structure);
|
||||
}
|
||||
|
||||
// Generate Capability class
|
||||
this.generateCapabilityClass();
|
||||
|
||||
// Generate capabilities map
|
||||
this.generateCapabilitiesMap(metaModel);
|
||||
|
||||
// Generate API contract
|
||||
this.generateApiContract(metaModel);
|
||||
|
||||
// Write types file
|
||||
const srcDir = path.join(__dirname, '..', 'src');
|
||||
if (!fs.existsSync(srcDir)) {
|
||||
fs.mkdirSync(srcDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(path.join(srcDir, 'types.ts'), this.writer.toString());
|
||||
|
||||
console.log('Generated LSP types file: src/types.ts');
|
||||
}
|
||||
}
|
||||
|
||||
// Run the generator
|
||||
if (require.main === module) {
|
||||
const generator = new LSPTypesGenerator();
|
||||
generator.generate();
|
||||
}
|
||||
1564
monaco-lsp-client/package-lock.json
generated
Normal file
1564
monaco-lsp-client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
monaco-lsp-client/package.json
Normal file
26
monaco-lsp-client/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "@vscode/monaco-lsp-client",
|
||||
"description": "description",
|
||||
"authors": "vscode",
|
||||
"version": "0.1.0",
|
||||
"main": "out/index.js",
|
||||
"types": "out/index.d.ts",
|
||||
"dependencies": {
|
||||
"@hediet/json-rpc": "^0.5.0",
|
||||
"@hediet/json-rpc-browser": "^0.5.1",
|
||||
"@hediet/json-rpc-websocket": "^0.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"monaco-editor-core": "^0.54.0-dev-20250929"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rolldown": "^1.0.0-beta.41",
|
||||
"rolldown-plugin-dts": "^0.16.11",
|
||||
"rollup-plugin-delete": "^3.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npx rolldown -c rolldown.config.mjs",
|
||||
"dev": "npx rolldown -c rolldown.config.mjs --watch",
|
||||
"generate": "tsx generator/index.ts"
|
||||
}
|
||||
}
|
||||
33
monaco-lsp-client/rolldown.config.mjs
Normal file
33
monaco-lsp-client/rolldown.config.mjs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// @ts-check
|
||||
|
||||
import { join } from 'path';
|
||||
import { defineConfig } from 'rolldown';
|
||||
import { dts } from 'rolldown-plugin-dts';
|
||||
import del from 'rollup-plugin-delete';
|
||||
import alias from '@rollup/plugin-alias';
|
||||
|
||||
export default defineConfig({
|
||||
input: {
|
||||
index: join(import.meta.dirname, './src/index.ts')
|
||||
},
|
||||
output: {
|
||||
dir: join(import.meta.dirname, './out'),
|
||||
format: 'es'
|
||||
},
|
||||
external: ['monaco-editor-core'],
|
||||
plugins: [
|
||||
del({ targets: 'out/*' }),
|
||||
alias({
|
||||
entries: {
|
||||
ws: 'undefined'
|
||||
}
|
||||
}),
|
||||
dts({
|
||||
tsconfig: false,
|
||||
compilerOptions: {
|
||||
stripInternal: true
|
||||
},
|
||||
resolve: true
|
||||
})
|
||||
]
|
||||
});
|
||||
40
monaco-lsp-client/src/adapters/ITextModelBridge.ts
Normal file
40
monaco-lsp-client/src/adapters/ITextModelBridge.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { Position, Range, TextDocumentIdentifier } from '../../src/types';
|
||||
|
||||
export interface ITextModelBridge {
|
||||
translate(
|
||||
textModel: monaco.editor.ITextModel,
|
||||
monacoPos: monaco.Position
|
||||
): {
|
||||
textDocument: TextDocumentIdentifier;
|
||||
position: Position;
|
||||
};
|
||||
|
||||
translateRange(textModel: monaco.editor.ITextModel, monacoRange: monaco.Range): Range;
|
||||
|
||||
translateBack(
|
||||
textDocument: TextDocumentIdentifier,
|
||||
position: Position
|
||||
): {
|
||||
textModel: monaco.editor.ITextModel;
|
||||
position: monaco.Position;
|
||||
};
|
||||
|
||||
translateBackRange(
|
||||
textDocument: TextDocumentIdentifier,
|
||||
range: Range
|
||||
): {
|
||||
textModel: monaco.editor.ITextModel;
|
||||
range: monaco.Range;
|
||||
};
|
||||
}
|
||||
|
||||
export function assertTargetTextModel<T extends { textModel: monaco.editor.ITextModel }>(
|
||||
input: T,
|
||||
expectedTextModel: monaco.editor.ITextModel
|
||||
): T {
|
||||
if (input.textModel !== expectedTextModel) {
|
||||
throw new Error(`Expected text model to be ${expectedTextModel}, but got ${input.textModel}`);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
254
monaco-lsp-client/src/adapters/LspCapabilitiesRegistry.ts
Normal file
254
monaco-lsp-client/src/adapters/LspCapabilitiesRegistry.ts
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
import { TypedChannel } from '@hediet/json-rpc';
|
||||
import { ClientCapabilities, Capability, ServerCapabilities, api, capabilities, TextDocumentChangeRegistrationOptions, TextDocumentSyncKind } from '../../src/types';
|
||||
import { IDisposable, Disposable } from '../utils';
|
||||
|
||||
export interface ILspCapabilitiesRegistry {
|
||||
addStaticClientCapabilities(capability: ClientCapabilities): IDisposable;
|
||||
registerCapabilityHandler<T>(capability: Capability<T>, handleStaticCapability: boolean, handler: (capability: T) => IDisposable): IDisposable;
|
||||
}
|
||||
|
||||
export class LspCapabilitiesRegistry extends Disposable implements ILspCapabilitiesRegistry {
|
||||
private readonly _staticCapabilities = new Set<{ cap: ClientCapabilities; }>();
|
||||
private readonly _dynamicFromStatic = DynamicFromStaticOptions.create();
|
||||
private readonly _registrations = new Map<Capability<any>, CapabilityInfo<any>>();
|
||||
private _serverCapabilities: ServerCapabilities | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _connection: TypedChannel
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.registerRequestHandler(api.client.clientRegisterCapability, async (params) => {
|
||||
for (const registration of params.registrations) {
|
||||
const capability = getCapabilityByMethod(registration.method);
|
||||
const r = new CapabilityRegistration(registration.id, capability, registration.registerOptions, false);
|
||||
this._registerCapabilityOptions(r);
|
||||
}
|
||||
return { ok: null };
|
||||
}));
|
||||
|
||||
this._register(this._connection.registerRequestHandler(api.client.clientUnregisterCapability, async (params) => {
|
||||
for (const unregistration of params.unregisterations) {
|
||||
const capability = getCapabilityByMethod(unregistration.method);
|
||||
const info = this._registrations.get(capability);
|
||||
const handlerInfo = info?.registrations.get(unregistration.id);
|
||||
if (!handlerInfo) {
|
||||
throw new Error(`No registration for method ${unregistration.method} with id ${unregistration.id}`);
|
||||
}
|
||||
handlerInfo?.handlerDisposables.forEach(d => d.dispose());
|
||||
info?.registrations.delete(unregistration.id);
|
||||
}
|
||||
return { ok: null };
|
||||
}));
|
||||
}
|
||||
|
||||
private _registerCapabilityOptions<T>(registration: CapabilityRegistration<T>) {
|
||||
let registrationForMethod = this._registrations.get(registration.capability);
|
||||
if (!registrationForMethod) {
|
||||
registrationForMethod = new CapabilityInfo();
|
||||
this._registrations.set(registration.capability, registrationForMethod);
|
||||
}
|
||||
if (registrationForMethod.registrations.has(registration.id)) {
|
||||
throw new Error(`Handler for method ${registration.capability.method} with id ${registration.id} already registered`);
|
||||
}
|
||||
registrationForMethod.registrations.set(registration.id, registration);
|
||||
for (const h of registrationForMethod.handlers) {
|
||||
if (!h.handleStaticCapability && registration.isFromStatic) {
|
||||
continue;
|
||||
}
|
||||
registration.handlerDisposables.set(h, h.handler(registration.options));
|
||||
}
|
||||
}
|
||||
|
||||
setServerCapabilities(serverCapabilities: ServerCapabilities) {
|
||||
if (this._serverCapabilities) {
|
||||
throw new Error('Server capabilities already set');
|
||||
}
|
||||
this._serverCapabilities = serverCapabilities;
|
||||
for (const cap of Object.values(capabilities)) {
|
||||
const options = this._dynamicFromStatic.getOptions(cap, serverCapabilities);
|
||||
if (options) {
|
||||
this._registerCapabilityOptions(new CapabilityRegistration(cap.method, cap, options, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getClientCapabilities(): ClientCapabilities {
|
||||
const result: ClientCapabilities = {};
|
||||
for (const c of this._staticCapabilities) {
|
||||
deepAssign(result, c.cap);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
addStaticClientCapabilities(capability: ClientCapabilities): IDisposable {
|
||||
const obj = { cap: capability };
|
||||
this._staticCapabilities.add(obj);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._staticCapabilities.delete(obj);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registerCapabilityHandler<T>(capability: Capability<T>, handleStaticCapability: boolean, handler: (capability: T) => IDisposable): IDisposable {
|
||||
let info = this._registrations.get(capability);
|
||||
if (!info) {
|
||||
info = new CapabilityInfo();
|
||||
this._registrations.set(capability, info);
|
||||
}
|
||||
const handlerInfo = new CapabilityHandler(capability, handleStaticCapability, handler);
|
||||
info.handlers.add(handlerInfo);
|
||||
|
||||
for (const registration of info.registrations.values()) {
|
||||
if (!handlerInfo.handleStaticCapability && registration.isFromStatic) {
|
||||
continue;
|
||||
}
|
||||
registration.handlerDisposables.set(handlerInfo, handler(registration.options));
|
||||
}
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
info.handlers.delete(handlerInfo);
|
||||
for (const registration of info.registrations.values()) {
|
||||
const disposable = registration.handlerDisposables.get(handlerInfo);
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
registration.handlerDisposables.delete(handlerInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CapabilityHandler<T> {
|
||||
constructor(
|
||||
public readonly capability: Capability<T>,
|
||||
public readonly handleStaticCapability: boolean,
|
||||
public readonly handler: (capabilityOptions: T) => IDisposable
|
||||
) { }
|
||||
}
|
||||
|
||||
class CapabilityRegistration<T> {
|
||||
public readonly handlerDisposables = new Map<CapabilityHandler<any>, IDisposable>();
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly capability: Capability<T>,
|
||||
public readonly options: T,
|
||||
public readonly isFromStatic: boolean
|
||||
) { }
|
||||
}
|
||||
|
||||
const capabilitiesByMethod = new Map([...Object.values(capabilities)].map(c => [c.method, c]));
|
||||
function getCapabilityByMethod(method: string): Capability<any> {
|
||||
const c = capabilitiesByMethod.get(method);
|
||||
if (!c) {
|
||||
throw new Error(`No capability found for method ${method}`);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
class CapabilityInfo<T> {
|
||||
public readonly handlers = new Set<CapabilityHandler<T>>();
|
||||
public readonly registrations = new Map</* id */ string, CapabilityRegistration<T>>();
|
||||
}
|
||||
|
||||
class DynamicFromStaticOptions {
|
||||
private readonly _mappings = new Map</* method */ string, (serverCapabilities: ServerCapabilities) => any>();
|
||||
|
||||
public static create(): DynamicFromStaticOptions {
|
||||
const o = new DynamicFromStaticOptions();
|
||||
o.set(capabilities.textDocumentDidChange, s => {
|
||||
if (s.textDocumentSync === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof s.textDocumentSync === 'object') {
|
||||
return {
|
||||
syncKind: s.textDocumentSync.change ?? TextDocumentSyncKind.None,
|
||||
documentSelector: null,
|
||||
} satisfies TextDocumentChangeRegistrationOptions;
|
||||
} else {
|
||||
return {
|
||||
syncKind: s.textDocumentSync,
|
||||
documentSelector: null,
|
||||
} satisfies TextDocumentChangeRegistrationOptions;
|
||||
}
|
||||
return null!;
|
||||
});
|
||||
|
||||
o.set(capabilities.textDocumentCompletion, s => s.completionProvider);
|
||||
o.set(capabilities.textDocumentHover, s => s.hoverProvider);
|
||||
o.set(capabilities.textDocumentSignatureHelp, s => s.signatureHelpProvider);
|
||||
o.set(capabilities.textDocumentDefinition, s => s.definitionProvider);
|
||||
o.set(capabilities.textDocumentReferences, s => s.referencesProvider);
|
||||
o.set(capabilities.textDocumentDocumentHighlight, s => s.documentHighlightProvider);
|
||||
o.set(capabilities.textDocumentDocumentSymbol, s => s.documentSymbolProvider);
|
||||
o.set(capabilities.textDocumentCodeAction, s => s.codeActionProvider);
|
||||
o.set(capabilities.textDocumentCodeLens, s => s.codeLensProvider);
|
||||
o.set(capabilities.textDocumentDocumentLink, s => s.documentLinkProvider);
|
||||
o.set(capabilities.textDocumentFormatting, s => s.documentFormattingProvider);
|
||||
o.set(capabilities.textDocumentRangeFormatting, s => s.documentRangeFormattingProvider);
|
||||
o.set(capabilities.textDocumentOnTypeFormatting, s => s.documentOnTypeFormattingProvider);
|
||||
o.set(capabilities.textDocumentRename, s => s.renameProvider);
|
||||
o.set(capabilities.textDocumentFoldingRange, s => s.foldingRangeProvider);
|
||||
o.set(capabilities.textDocumentDeclaration, s => s.declarationProvider);
|
||||
o.set(capabilities.textDocumentTypeDefinition, s => s.typeDefinitionProvider);
|
||||
o.set(capabilities.textDocumentImplementation, s => s.implementationProvider);
|
||||
o.set(capabilities.textDocumentDocumentColor, s => s.colorProvider);
|
||||
o.set(capabilities.textDocumentSelectionRange, s => s.selectionRangeProvider);
|
||||
o.set(capabilities.textDocumentLinkedEditingRange, s => s.linkedEditingRangeProvider);
|
||||
o.set(capabilities.textDocumentPrepareCallHierarchy, s => s.callHierarchyProvider);
|
||||
o.set(capabilities.textDocumentSemanticTokensFull, s => s.semanticTokensProvider);
|
||||
o.set(capabilities.textDocumentInlayHint, s => s.inlayHintProvider);
|
||||
o.set(capabilities.textDocumentInlineValue, s => s.inlineValueProvider);
|
||||
o.set(capabilities.textDocumentDiagnostic, s => s.diagnosticProvider);
|
||||
o.set(capabilities.textDocumentMoniker, s => s.monikerProvider);
|
||||
o.set(capabilities.textDocumentPrepareTypeHierarchy, s => s.typeHierarchyProvider);
|
||||
o.set(capabilities.workspaceSymbol, s => s.workspaceSymbolProvider);
|
||||
o.set(capabilities.workspaceExecuteCommand, s => s.executeCommandProvider);
|
||||
return o;
|
||||
}
|
||||
|
||||
set<T>(capability: Capability<T>, getOptionsFromStatic: (serverCapabilities: ServerCapabilities) => T | boolean | undefined): void {
|
||||
if (this._mappings.has(capability.method)) {
|
||||
throw new Error(`Capability for method ${capability.method} already registered`);
|
||||
}
|
||||
this._mappings.set(capability.method, getOptionsFromStatic);
|
||||
}
|
||||
|
||||
getOptions<T>(capability: Capability<T>, serverCapabilities: ServerCapabilities): T | undefined {
|
||||
const getter = this._mappings.get(capability.method);
|
||||
if (!getter) {
|
||||
return undefined;
|
||||
}
|
||||
const result = getter(serverCapabilities);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function deepAssign(target: any, source: any) {
|
||||
for (const key of Object.keys(source)) {
|
||||
const srcValue = source[key];
|
||||
if (srcValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
const tgtValue = target[key];
|
||||
if (tgtValue === undefined) {
|
||||
target[key] = srcValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof srcValue !== 'object' || srcValue === null) {
|
||||
target[key] = srcValue;
|
||||
continue;
|
||||
}
|
||||
if (typeof tgtValue !== 'object' || tgtValue === null) {
|
||||
target[key] = srcValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
deepAssign(tgtValue, srcValue);
|
||||
}
|
||||
}
|
||||
90
monaco-lsp-client/src/adapters/LspClient.ts
Normal file
90
monaco-lsp-client/src/adapters/LspClient.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { IMessageTransport, TypedChannel } from "@hediet/json-rpc";
|
||||
import { LspCompletionFeature } from "./languageFeatures/LspCompletionFeature";
|
||||
import { LspHoverFeature } from "./languageFeatures/LspHoverFeature";
|
||||
import { LspSignatureHelpFeature } from "./languageFeatures/LspSignatureHelpFeature";
|
||||
import { LspDefinitionFeature } from "./languageFeatures/LspDefinitionFeature";
|
||||
import { LspDeclarationFeature } from "./languageFeatures/LspDeclarationFeature";
|
||||
import { LspTypeDefinitionFeature } from "./languageFeatures/LspTypeDefinitionFeature";
|
||||
import { LspImplementationFeature } from "./languageFeatures/LspImplementationFeature";
|
||||
import { LspReferencesFeature } from "./languageFeatures/LspReferencesFeature";
|
||||
import { LspDocumentHighlightFeature } from "./languageFeatures/LspDocumentHighlightFeature";
|
||||
import { LspDocumentSymbolFeature } from "./languageFeatures/LspDocumentSymbolFeature";
|
||||
import { LspRenameFeature } from "./languageFeatures/LspRenameFeature";
|
||||
import { LspCodeActionFeature } from "./languageFeatures/LspCodeActionFeature";
|
||||
import { LspCodeLensFeature } from "./languageFeatures/LspCodeLensFeature";
|
||||
import { LspDocumentLinkFeature } from "./languageFeatures/LspDocumentLinkFeature";
|
||||
import { LspFormattingFeature } from "./languageFeatures/LspFormattingFeature";
|
||||
import { LspRangeFormattingFeature } from "./languageFeatures/LspRangeFormattingFeature";
|
||||
import { LspOnTypeFormattingFeature } from "./languageFeatures/LspOnTypeFormattingFeature";
|
||||
import { LspFoldingRangeFeature } from "./languageFeatures/LspFoldingRangeFeature";
|
||||
import { LspSelectionRangeFeature } from "./languageFeatures/LspSelectionRangeFeature";
|
||||
import { LspInlayHintsFeature } from "./languageFeatures/LspInlayHintsFeature";
|
||||
import { LspSemanticTokensFeature } from "./languageFeatures/LspSemanticTokensFeature";
|
||||
import { LspDiagnosticsFeature } from "./languageFeatures/LspDiagnosticsFeature";
|
||||
import { api } from "../../src/types";
|
||||
import { LspConnection } from "./LspConnection";
|
||||
import { LspCapabilitiesRegistry } from './LspCapabilitiesRegistry';
|
||||
import { TextDocumentSynchronizer } from "./TextDocumentSynchronizer";
|
||||
import { DisposableStore, IDisposable } from "../utils";
|
||||
|
||||
export class MonacoLspClient {
|
||||
private _connection: LspConnection;
|
||||
private readonly _capabilitiesRegistry: LspCapabilitiesRegistry;
|
||||
private readonly _bridge: TextDocumentSynchronizer;
|
||||
|
||||
private _initPromise: Promise<void>;
|
||||
|
||||
constructor(transport: IMessageTransport) {
|
||||
const c = TypedChannel.fromTransport(transport);
|
||||
const s = api.getServer(c, {});
|
||||
c.startListen();
|
||||
|
||||
this._capabilitiesRegistry = new LspCapabilitiesRegistry(c);
|
||||
this._bridge = new TextDocumentSynchronizer(s.server, this._capabilitiesRegistry);
|
||||
|
||||
this._connection = new LspConnection(s.server, this._bridge, this._capabilitiesRegistry, c);
|
||||
this.createFeatures();
|
||||
|
||||
this._initPromise = this._init();
|
||||
}
|
||||
|
||||
private async _init() {
|
||||
const result = await this._connection.server.initialize({
|
||||
processId: null,
|
||||
capabilities: this._capabilitiesRegistry.getClientCapabilities(),
|
||||
rootUri: null,
|
||||
});
|
||||
|
||||
this._connection.server.initialized({});
|
||||
this._capabilitiesRegistry.setServerCapabilities(result.capabilities);
|
||||
}
|
||||
|
||||
protected createFeatures(): IDisposable {
|
||||
const store = new DisposableStore();
|
||||
|
||||
store.add(new LspCompletionFeature(this._connection));
|
||||
store.add(new LspHoverFeature(this._connection));
|
||||
store.add(new LspSignatureHelpFeature(this._connection));
|
||||
store.add(new LspDefinitionFeature(this._connection));
|
||||
store.add(new LspDeclarationFeature(this._connection));
|
||||
store.add(new LspTypeDefinitionFeature(this._connection));
|
||||
store.add(new LspImplementationFeature(this._connection));
|
||||
store.add(new LspReferencesFeature(this._connection));
|
||||
store.add(new LspDocumentHighlightFeature(this._connection));
|
||||
store.add(new LspDocumentSymbolFeature(this._connection));
|
||||
store.add(new LspRenameFeature(this._connection));
|
||||
store.add(new LspCodeActionFeature(this._connection));
|
||||
store.add(new LspCodeLensFeature(this._connection));
|
||||
store.add(new LspDocumentLinkFeature(this._connection));
|
||||
store.add(new LspFormattingFeature(this._connection));
|
||||
store.add(new LspRangeFormattingFeature(this._connection));
|
||||
store.add(new LspOnTypeFormattingFeature(this._connection));
|
||||
store.add(new LspFoldingRangeFeature(this._connection));
|
||||
store.add(new LspSelectionRangeFeature(this._connection));
|
||||
store.add(new LspInlayHintsFeature(this._connection));
|
||||
store.add(new LspSemanticTokensFeature(this._connection));
|
||||
store.add(new LspDiagnosticsFeature(this._connection));
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
13
monaco-lsp-client/src/adapters/LspConnection.ts
Normal file
13
monaco-lsp-client/src/adapters/LspConnection.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { TypedChannel } from '@hediet/json-rpc';
|
||||
import { api } from '../../src/types';
|
||||
import { ITextModelBridge } from './ITextModelBridge';
|
||||
import { LspCapabilitiesRegistry } from './LspCapabilitiesRegistry';
|
||||
|
||||
export class LspConnection {
|
||||
constructor(
|
||||
public readonly server: typeof api.TServerInterface,
|
||||
public readonly bridge: ITextModelBridge,
|
||||
public readonly capabilities: LspCapabilitiesRegistry,
|
||||
public readonly connection: TypedChannel,
|
||||
) { }
|
||||
}
|
||||
183
monaco-lsp-client/src/adapters/TextDocumentSynchronizer.ts
Normal file
183
monaco-lsp-client/src/adapters/TextDocumentSynchronizer.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { api, capabilities, Position, Range, TextDocumentContentChangeEvent, TextDocumentIdentifier } from '../../src/types';
|
||||
import { Disposable } from '../utils';
|
||||
import { ITextModelBridge } from './ITextModelBridge';
|
||||
import { ILspCapabilitiesRegistry } from './LspCapabilitiesRegistry';
|
||||
|
||||
export class TextDocumentSynchronizer extends Disposable implements ITextModelBridge {
|
||||
private readonly _managedModels = new Map<monaco.editor.ITextModel, ManagedModel>();
|
||||
private readonly _managedModelsReverse = new Map</* uri */ string, monaco.editor.ITextModel>();
|
||||
|
||||
private _started = false;
|
||||
|
||||
constructor(
|
||||
private readonly _server: typeof api.TServerInterface,
|
||||
private readonly _capabilities: ILspCapabilitiesRegistry,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
synchronization: {
|
||||
dynamicRegistration: true,
|
||||
willSave: false,
|
||||
willSaveWaitUntil: false,
|
||||
didSave: false,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(_capabilities.registerCapabilityHandler(capabilities.textDocumentDidChange, true, e => {
|
||||
if (this._started) {
|
||||
return {
|
||||
dispose: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
this._started = true;
|
||||
this._register(monaco.editor.onDidCreateModel(m => {
|
||||
this._getOrCreateManagedModel(m);
|
||||
}));
|
||||
for (const m of monaco.editor.getModels()) {
|
||||
this._getOrCreateManagedModel(m);
|
||||
}
|
||||
return {
|
||||
dispose: () => {
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _getOrCreateManagedModel(m: monaco.editor.ITextModel) {
|
||||
if (!this._started) {
|
||||
throw new Error('Not started');
|
||||
}
|
||||
|
||||
const uriStr = m.uri.toString(true).toLowerCase();
|
||||
let mm = this._managedModels.get(m);
|
||||
if (!mm) {
|
||||
mm = new ManagedModel(m, this._server);
|
||||
this._managedModels.set(m, mm);
|
||||
this._managedModelsReverse.set(uriStr, m);
|
||||
}
|
||||
m.onWillDispose(() => {
|
||||
mm!.dispose();
|
||||
this._managedModels.delete(m);
|
||||
this._managedModelsReverse.delete(uriStr);
|
||||
});
|
||||
return mm;
|
||||
}
|
||||
|
||||
translateBack(textDocument: TextDocumentIdentifier, position: Position): { textModel: monaco.editor.ITextModel; position: monaco.Position; } {
|
||||
const uri = textDocument.uri.toLowerCase();
|
||||
const textModel = this._managedModelsReverse.get(uri);
|
||||
if (!textModel) {
|
||||
throw new Error(`No text model for uri ${uri}`);
|
||||
}
|
||||
const monacoPosition = new monaco.Position(position.line + 1, position.character + 1);
|
||||
return { textModel, position: monacoPosition };
|
||||
}
|
||||
|
||||
translateBackRange(textDocument: TextDocumentIdentifier, range: Range): { textModel: monaco.editor.ITextModel; range: monaco.Range; } {
|
||||
const uri = textDocument.uri.toLowerCase();
|
||||
const textModel = this._managedModelsReverse.get(uri);
|
||||
if (!textModel) {
|
||||
throw new Error(`No text model for uri ${uri}`);
|
||||
}
|
||||
const monacoRange = new monaco.Range(
|
||||
range.start.line + 1,
|
||||
range.start.character + 1,
|
||||
range.end.line + 1,
|
||||
range.end.character + 1
|
||||
);
|
||||
return { textModel, range: monacoRange };
|
||||
}
|
||||
|
||||
translate(textModel: monaco.editor.ITextModel, monacoPos: monaco.Position): { textDocument: TextDocumentIdentifier; position: Position; } {
|
||||
return {
|
||||
textDocument: {
|
||||
uri: textModel.uri.toString(true),
|
||||
},
|
||||
position: {
|
||||
line: monacoPos.lineNumber - 1,
|
||||
character: monacoPos.column - 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
translateRange(textModel: monaco.editor.ITextModel, monacoRange: monaco.Range): Range {
|
||||
return {
|
||||
start: {
|
||||
line: monacoRange.startLineNumber - 1,
|
||||
character: monacoRange.startColumn - 1,
|
||||
},
|
||||
end: {
|
||||
line: monacoRange.endLineNumber - 1,
|
||||
character: monacoRange.endColumn - 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ManagedModel extends Disposable {
|
||||
constructor(
|
||||
private readonly _textModel: monaco.editor.ITextModel,
|
||||
private readonly _api: typeof api.TServerInterface
|
||||
) {
|
||||
super();
|
||||
|
||||
const uri = _textModel.uri.toString(true).toLowerCase();
|
||||
|
||||
this._api.textDocumentDidOpen({
|
||||
textDocument: {
|
||||
languageId: _textModel.getLanguageId(),
|
||||
uri: uri,
|
||||
version: _textModel.getVersionId(),
|
||||
text: _textModel.getValue(),
|
||||
}
|
||||
});
|
||||
|
||||
this._register(_textModel.onDidChangeContent(e => {
|
||||
const contentChanges = e.changes.map(c => toLspTextDocumentContentChangeEvent(c));
|
||||
|
||||
this._api.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: uri,
|
||||
version: _textModel.getVersionId(),
|
||||
},
|
||||
contentChanges: contentChanges
|
||||
});
|
||||
}));
|
||||
|
||||
this._register({
|
||||
dispose: () => {
|
||||
this._api.textDocumentDidClose({
|
||||
textDocument: {
|
||||
uri: uri,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toLspTextDocumentContentChangeEvent(change: monaco.editor.IModelContentChange): TextDocumentContentChangeEvent {
|
||||
return {
|
||||
range: toLspRange(change.range),
|
||||
rangeLength: change.rangeLength,
|
||||
text: change.text,
|
||||
};
|
||||
}
|
||||
|
||||
function toLspRange(range: monaco.IRange): Range {
|
||||
return {
|
||||
start: {
|
||||
line: range.startLineNumber - 1,
|
||||
character: range.startColumn - 1,
|
||||
},
|
||||
end: {
|
||||
line: range.endLineNumber - 1,
|
||||
character: range.endColumn - 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, CodeActionRegistrationOptions, Command, WorkspaceEdit, CodeAction } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { lspCodeActionKindToMonacoCodeActionKind, toMonacoCodeActionKind, toLspDiagnosticSeverity, toLspCodeActionTriggerKind, toMonacoCommand } from './common';
|
||||
|
||||
export class LspCodeActionFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
codeAction: {
|
||||
dynamicRegistration: true,
|
||||
codeActionLiteralSupport: {
|
||||
codeActionKind: {
|
||||
valueSet: Array.from(lspCodeActionKindToMonacoCodeActionKind.keys()),
|
||||
}
|
||||
},
|
||||
isPreferredSupport: true,
|
||||
disabledSupport: true,
|
||||
dataSupport: true,
|
||||
resolveSupport: {
|
||||
properties: ['edit'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentCodeAction, true, capability => {
|
||||
return monaco.languages.registerCodeActionProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspCodeActionProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtendedCodeAction extends monaco.languages.CodeAction {
|
||||
_lspAction?: CodeAction;
|
||||
}
|
||||
|
||||
class LspCodeActionProvider implements monaco.languages.CodeActionProvider {
|
||||
public readonly resolveCodeAction;
|
||||
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: CodeActionRegistrationOptions,
|
||||
) {
|
||||
if (_capabilities.resolveProvider) {
|
||||
this.resolveCodeAction = async (codeAction: ExtendedCodeAction, token: monaco.CancellationToken): Promise<ExtendedCodeAction> => {
|
||||
if (codeAction._lspAction) {
|
||||
const resolved = await this._client.server.codeActionResolve(codeAction._lspAction);
|
||||
if (resolved.edit) {
|
||||
codeAction.edit = toMonacoWorkspaceEdit(resolved.edit, this._client);
|
||||
}
|
||||
if (resolved.command) {
|
||||
codeAction.command = toMonacoCommand(resolved.command);
|
||||
}
|
||||
}
|
||||
return codeAction;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async provideCodeActions(
|
||||
model: monaco.editor.ITextModel,
|
||||
range: monaco.Range,
|
||||
context: monaco.languages.CodeActionContext,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.CodeActionList | null> {
|
||||
const translated = this._client.bridge.translate(model, range.getStartPosition());
|
||||
|
||||
const result = await this._client.server.textDocumentCodeAction({
|
||||
textDocument: translated.textDocument,
|
||||
range: this._client.bridge.translateRange(model, range),
|
||||
context: {
|
||||
diagnostics: context.markers.map(marker => ({
|
||||
range: this._client.bridge.translateRange(model, monaco.Range.lift(marker)),
|
||||
message: marker.message,
|
||||
severity: toLspDiagnosticSeverity(marker.severity),
|
||||
})),
|
||||
triggerKind: toLspCodeActionTriggerKind(context.trigger),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const actions = Array.isArray(result) ? result : [result];
|
||||
|
||||
return {
|
||||
actions: actions.map(action => {
|
||||
if ('title' in action && !('kind' in action)) {
|
||||
// Command
|
||||
const cmd = action as Command;
|
||||
const monacoAction: ExtendedCodeAction = {
|
||||
title: cmd.title,
|
||||
command: toMonacoCommand(cmd),
|
||||
};
|
||||
return monacoAction;
|
||||
} else {
|
||||
// CodeAction
|
||||
const codeAction = action as CodeAction;
|
||||
const monacoAction: ExtendedCodeAction = {
|
||||
title: codeAction.title,
|
||||
kind: toMonacoCodeActionKind(codeAction.kind),
|
||||
isPreferred: codeAction.isPreferred,
|
||||
disabled: codeAction.disabled?.reason,
|
||||
edit: codeAction.edit ? toMonacoWorkspaceEdit(codeAction.edit, this._client) : undefined,
|
||||
command: toMonacoCommand(codeAction.command),
|
||||
_lspAction: codeAction,
|
||||
};
|
||||
return monacoAction;
|
||||
}
|
||||
}),
|
||||
dispose: () => { },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function toMonacoWorkspaceEdit(
|
||||
edit: WorkspaceEdit,
|
||||
client: LspConnection
|
||||
): monaco.languages.WorkspaceEdit {
|
||||
const edits: monaco.languages.IWorkspaceTextEdit[] = [];
|
||||
|
||||
if (edit.changes) {
|
||||
for (const uri in edit.changes) {
|
||||
const textEdits = edit.changes[uri];
|
||||
for (const textEdit of textEdits) {
|
||||
const translated = client.bridge.translateBackRange({ uri }, textEdit.range);
|
||||
edits.push({
|
||||
resource: translated.textModel.uri,
|
||||
versionId: undefined,
|
||||
textEdit: {
|
||||
range: translated.range,
|
||||
text: textEdit.newText,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edit.documentChanges) {
|
||||
for (const change of edit.documentChanges) {
|
||||
if ('textDocument' in change) {
|
||||
const uri = change.textDocument.uri;
|
||||
for (const textEdit of change.edits) {
|
||||
const translated = client.bridge.translateBackRange({ uri }, textEdit.range);
|
||||
edits.push({
|
||||
resource: translated.textModel.uri,
|
||||
versionId: change.textDocument.version ?? undefined,
|
||||
textEdit: {
|
||||
range: translated.range,
|
||||
text: textEdit.newText,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { edits };
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, CodeLensRegistrationOptions, CodeLens } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { assertTargetTextModel } from '../ITextModelBridge';
|
||||
import { toMonacoCommand } from './common';
|
||||
|
||||
export class LspCodeLensFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
codeLens: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentCodeLens, true, capability => {
|
||||
return monaco.languages.registerCodeLensProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspCodeLensProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtendedCodeLens extends monaco.languages.CodeLens {
|
||||
_lspCodeLens?: CodeLens;
|
||||
}
|
||||
|
||||
class LspCodeLensProvider implements monaco.languages.CodeLensProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: CodeLensRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideCodeLenses(
|
||||
model: monaco.editor.ITextModel,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.CodeLensList | null> {
|
||||
const translated = this._client.bridge.translate(model, new monaco.Position(1, 1));
|
||||
|
||||
const result = await this._client.server.textDocumentCodeLens({
|
||||
textDocument: translated.textDocument,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
lenses: result.map(lens => {
|
||||
const monacoLens: ExtendedCodeLens = {
|
||||
range: assertTargetTextModel(this._client.bridge.translateBackRange(translated.textDocument, lens.range), model).range,
|
||||
command: toMonacoCommand(lens.command),
|
||||
_lspCodeLens: lens,
|
||||
};
|
||||
return monacoLens;
|
||||
}),
|
||||
dispose: () => { },
|
||||
};
|
||||
}
|
||||
|
||||
async resolveCodeLens(
|
||||
model: monaco.editor.ITextModel,
|
||||
codeLens: ExtendedCodeLens,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.CodeLens> {
|
||||
if (!this._capabilities.resolveProvider || !codeLens._lspCodeLens) {
|
||||
return codeLens;
|
||||
}
|
||||
|
||||
const resolved = await this._client.server.codeLensResolve(codeLens._lspCodeLens);
|
||||
|
||||
if (resolved.command) {
|
||||
codeLens.command = {
|
||||
id: resolved.command.command,
|
||||
title: resolved.command.title,
|
||||
arguments: resolved.command.arguments,
|
||||
};
|
||||
}
|
||||
|
||||
return codeLens;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, CompletionRegistrationOptions, MarkupContent, CompletionItem, TextDocumentPositionParams } from '../../../src/types';
|
||||
import { assertTargetTextModel, ITextModelBridge } from '../ITextModelBridge';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import {
|
||||
lspCompletionItemKindToMonacoCompletionItemKind,
|
||||
lspCompletionItemTagToMonacoCompletionItemTag,
|
||||
toMonacoCompletionItemKind,
|
||||
toMonacoCompletionItemTag,
|
||||
toLspCompletionTriggerKind,
|
||||
toMonacoInsertTextRules,
|
||||
toMonacoCommand,
|
||||
} from './common';
|
||||
|
||||
export class LspCompletionFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
completion: {
|
||||
dynamicRegistration: true,
|
||||
contextSupport: true,
|
||||
completionItemKind: {
|
||||
valueSet: Array.from(lspCompletionItemKindToMonacoCompletionItemKind.keys()),
|
||||
},
|
||||
completionItem: {
|
||||
tagSupport: {
|
||||
valueSet: Array.from(lspCompletionItemTagToMonacoCompletionItemTag.keys()),
|
||||
},
|
||||
commitCharactersSupport: true,
|
||||
deprecatedSupport: true,
|
||||
preselectSupport: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentCompletion, true, capability => {
|
||||
return monaco.languages.registerCompletionItemProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspCompletionProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtendedCompletionItem extends monaco.languages.CompletionItem {
|
||||
_lspItem: CompletionItem;
|
||||
_translated: TextDocumentPositionParams;
|
||||
_model: monaco.editor.ITextModel;
|
||||
}
|
||||
|
||||
class LspCompletionProvider implements monaco.languages.CompletionItemProvider {
|
||||
public readonly resolveCompletionItem;
|
||||
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: CompletionRegistrationOptions,
|
||||
) {
|
||||
if (_capabilities.resolveProvider) {
|
||||
this.resolveCompletionItem = async (item: ExtendedCompletionItem, token: monaco.CancellationToken): Promise<ExtendedCompletionItem> => {
|
||||
const resolved = await this._client.server.completionItemResolve(item._lspItem);
|
||||
applyLspCompletionItemProperties(item, resolved, this._client.bridge, item._translated, item._model);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get triggerCharacters(): string[] | undefined {
|
||||
return this._capabilities.triggerCharacters;
|
||||
}
|
||||
|
||||
async provideCompletionItems(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
context: monaco.languages.CompletionContext,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.CompletionList & { suggestions: ExtendedCompletionItem[] }> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentCompletion({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
context: context.triggerCharacter ? {
|
||||
triggerKind: toLspCompletionTriggerKind(context.triggerKind),
|
||||
triggerCharacter: context.triggerCharacter,
|
||||
} : undefined,
|
||||
});
|
||||
if (!result) {
|
||||
return { suggestions: [] };
|
||||
}
|
||||
|
||||
const items = Array.isArray(result) ? result : result.items;
|
||||
|
||||
return {
|
||||
suggestions: items.map<ExtendedCompletionItem>(i => {
|
||||
const item: ExtendedCompletionItem = {
|
||||
...convertLspToMonacoCompletionItem(
|
||||
i,
|
||||
this._client.bridge,
|
||||
translated,
|
||||
model,
|
||||
position
|
||||
),
|
||||
_lspItem: i,
|
||||
_translated: translated,
|
||||
_model: model,
|
||||
};
|
||||
|
||||
return item;
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function convertLspToMonacoCompletionItem(
|
||||
lspItem: CompletionItem,
|
||||
bridge: ITextModelBridge,
|
||||
translated: TextDocumentPositionParams,
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position
|
||||
): monaco.languages.CompletionItem {
|
||||
let insertText = lspItem.insertText || lspItem.label;
|
||||
let range: monaco.IRange | monaco.languages.CompletionItemRanges | undefined = undefined;
|
||||
|
||||
if (lspItem.textEdit) {
|
||||
if ('range' in lspItem.textEdit) {
|
||||
insertText = lspItem.textEdit.newText;
|
||||
range = assertTargetTextModel(bridge.translateBackRange(translated.textDocument, lspItem.textEdit.range), model).range;
|
||||
} else {
|
||||
insertText = lspItem.textEdit.newText;
|
||||
range = {
|
||||
insert: assertTargetTextModel(bridge.translateBackRange(translated.textDocument, lspItem.textEdit.insert), model).range,
|
||||
replace: assertTargetTextModel(bridge.translateBackRange(translated.textDocument, lspItem.textEdit.replace), model).range,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!range) {
|
||||
range = monaco.Range.fromPositions(position, position);
|
||||
}
|
||||
|
||||
const item: monaco.languages.CompletionItem = {
|
||||
label: lspItem.label,
|
||||
kind: toMonacoCompletionItemKind(lspItem.kind),
|
||||
insertText,
|
||||
sortText: lspItem.sortText,
|
||||
filterText: lspItem.filterText,
|
||||
preselect: lspItem.preselect,
|
||||
commitCharacters: lspItem.commitCharacters,
|
||||
range: range,
|
||||
};
|
||||
|
||||
applyLspCompletionItemProperties(item, lspItem, bridge, translated, model);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function applyLspCompletionItemProperties(
|
||||
monacoItem: monaco.languages.CompletionItem,
|
||||
lspItem: CompletionItem,
|
||||
bridge: ITextModelBridge,
|
||||
translated: TextDocumentPositionParams,
|
||||
targetModel: monaco.editor.ITextModel
|
||||
): void {
|
||||
if (lspItem.detail !== undefined) {
|
||||
monacoItem.detail = lspItem.detail;
|
||||
}
|
||||
if (lspItem.documentation !== undefined) {
|
||||
monacoItem.documentation = toMonacoDocumentation(lspItem.documentation);
|
||||
}
|
||||
if (lspItem.insertTextFormat !== undefined) {
|
||||
const insertTextRules = toMonacoInsertTextRules(lspItem.insertTextFormat);
|
||||
monacoItem.insertTextRules = insertTextRules;
|
||||
}
|
||||
if (lspItem.tags && lspItem.tags.length > 0) {
|
||||
monacoItem.tags = lspItem.tags.map(toMonacoCompletionItemTag).filter((tag): tag is monaco.languages.CompletionItemTag => tag !== undefined);
|
||||
}
|
||||
if (lspItem.additionalTextEdits && lspItem.additionalTextEdits.length > 0) {
|
||||
monacoItem.additionalTextEdits = lspItem.additionalTextEdits.map(edit => ({
|
||||
range: assertTargetTextModel(bridge.translateBackRange(translated.textDocument, edit.range), targetModel).range,
|
||||
text: edit.newText,
|
||||
}));
|
||||
}
|
||||
if (lspItem.command) {
|
||||
monacoItem.command = toMonacoCommand(lspItem.command);
|
||||
}
|
||||
}
|
||||
|
||||
function toMonacoDocumentation(doc: string | MarkupContent | undefined): string | monaco.IMarkdownString | undefined {
|
||||
if (!doc) return undefined;
|
||||
if (typeof doc === 'string') return doc;
|
||||
return {
|
||||
value: doc.value,
|
||||
isTrusted: true,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DeclarationRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toMonacoLocation } from "./common";
|
||||
|
||||
export class LspDeclarationFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
declaration: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentDeclaration, true, capability => {
|
||||
return monaco.languages.registerDeclarationProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDeclarationProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDeclarationProvider implements monaco.languages.DeclarationProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DeclarationRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideDeclaration(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.Definition | monaco.languages.LocationLink[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentDeclaration({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return result.map(loc => toMonacoLocation(loc, this._client));
|
||||
}
|
||||
|
||||
return toMonacoLocation(result, this._client);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DefinitionRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toMonacoLocation } from "./common";
|
||||
|
||||
export class LspDefinitionFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
definition: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentDefinition, true, capability => {
|
||||
return monaco.languages.registerDefinitionProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDefinitionProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDefinitionProvider implements monaco.languages.DefinitionProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DefinitionRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideDefinition(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.Definition | monaco.languages.LocationLink[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentDefinition({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return result.map(loc => toMonacoLocation(loc, this._client));
|
||||
}
|
||||
|
||||
return toMonacoLocation(result, this._client);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { api, capabilities, Diagnostic, DiagnosticRegistrationOptions, DocumentDiagnosticReport, PublishDiagnosticsParams } from '../../../src/types';
|
||||
import { Disposable, DisposableStore } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { lspDiagnosticTagToMonacoMarkerTag, matchesDocumentSelector, toDiagnosticMarker } from './common';
|
||||
|
||||
export class LspDiagnosticsFeature extends Disposable {
|
||||
private readonly _diagnosticsMarkerOwner = 'lsp';
|
||||
private readonly _pullDiagnosticProviders = new Map<monaco.editor.ITextModel, ModelDiagnosticProvider>();
|
||||
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
publishDiagnostics: {
|
||||
relatedInformation: true,
|
||||
tagSupport: {
|
||||
valueSet: [...lspDiagnosticTagToMonacoMarkerTag.keys()],
|
||||
},
|
||||
versionSupport: true,
|
||||
codeDescriptionSupport: true,
|
||||
dataSupport: true,
|
||||
},
|
||||
diagnostic: {
|
||||
dynamicRegistration: true,
|
||||
relatedDocumentSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
debugger;
|
||||
this._register(this._connection.connection.registerNotificationHandler(
|
||||
api.client.textDocumentPublishDiagnostics,
|
||||
(params) => this._handlePublishDiagnostics(params)
|
||||
));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(
|
||||
capabilities.textDocumentDiagnostic,
|
||||
true,
|
||||
(capability) => {
|
||||
const disposables = new DisposableStore();
|
||||
for (const model of monaco.editor.getModels()) {
|
||||
this._addPullDiagnosticProvider(model, capability, disposables);
|
||||
}
|
||||
disposables.add(monaco.editor.onDidCreateModel(model => {
|
||||
this._addPullDiagnosticProvider(model, capability, disposables);
|
||||
}));
|
||||
return disposables;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
private _addPullDiagnosticProvider(
|
||||
model: monaco.editor.ITextModel,
|
||||
capability: DiagnosticRegistrationOptions,
|
||||
disposables: DisposableStore
|
||||
): void {
|
||||
// Check if model matches the document selector
|
||||
const languageId = model.getLanguageId();
|
||||
|
||||
if (!matchesDocumentSelector(model, capability.documentSelector)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = new ModelDiagnosticProvider(
|
||||
model,
|
||||
this._connection,
|
||||
this._diagnosticsMarkerOwner,
|
||||
capability
|
||||
);
|
||||
|
||||
this._pullDiagnosticProviders.set(model, provider);
|
||||
disposables.add(provider);
|
||||
|
||||
disposables.add(model.onWillDispose(() => {
|
||||
this._pullDiagnosticProviders.delete(model);
|
||||
}));
|
||||
}
|
||||
|
||||
private _handlePublishDiagnostics(params: PublishDiagnosticsParams): void {
|
||||
const uri = params.uri;
|
||||
|
||||
try {
|
||||
const translated = this._connection.bridge.translateBack({ uri }, { line: 0, character: 0 });
|
||||
const model = translated.textModel;
|
||||
|
||||
if (!model || model.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const markers = params.diagnostics.map(diagnostic =>
|
||||
toDiagnosticMarker(diagnostic)
|
||||
);
|
||||
|
||||
monaco.editor.setModelMarkers(model, this._diagnosticsMarkerOwner, markers);
|
||||
} catch (error) {
|
||||
// Model not found or already disposed - this is normal when files are closed
|
||||
console.debug(`Could not set diagnostics for ${uri}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages pull diagnostics for a single text model
|
||||
*/
|
||||
class ModelDiagnosticProvider extends Disposable {
|
||||
private _updateHandle: number | undefined;
|
||||
private _previousResultId: string | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _model: monaco.editor.ITextModel,
|
||||
private readonly _connection: LspConnection,
|
||||
private readonly _markerOwner: string,
|
||||
private readonly _capability: DiagnosticRegistrationOptions,
|
||||
) {
|
||||
super();
|
||||
this._register(this._model.onDidChangeContent(() => {
|
||||
this._scheduleDiagnosticUpdate();
|
||||
}));
|
||||
this._scheduleDiagnosticUpdate();
|
||||
}
|
||||
|
||||
private _scheduleDiagnosticUpdate(): void {
|
||||
if (this._updateHandle !== undefined) {
|
||||
clearTimeout(this._updateHandle);
|
||||
}
|
||||
|
||||
this._updateHandle = window.setTimeout(() => {
|
||||
this._updateHandle = undefined;
|
||||
this._requestDiagnostics();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private async _requestDiagnostics(): Promise<void> {
|
||||
if (this._model.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const translated = this._connection.bridge.translate(this._model, new monaco.Position(1, 1));
|
||||
|
||||
const result = await this._connection.server.textDocumentDiagnostic({
|
||||
textDocument: translated.textDocument,
|
||||
identifier: this._capability.identifier,
|
||||
previousResultId: this._previousResultId,
|
||||
});
|
||||
|
||||
if (this._model.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._handleDiagnosticReport(result);
|
||||
} catch (error) {
|
||||
console.error('Error requesting diagnostics:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDiagnosticReport(report: DocumentDiagnosticReport): void {
|
||||
if (report.kind === 'full') {
|
||||
// Full diagnostic report
|
||||
this._previousResultId = report.resultId;
|
||||
|
||||
const markers = report.items.map(diagnostic => toDiagnosticMarker(diagnostic));
|
||||
monaco.editor.setModelMarkers(this._model, this._markerOwner, markers);
|
||||
|
||||
// Handle related documents if present
|
||||
if ('relatedDocuments' in report && report.relatedDocuments) {
|
||||
this._handleRelatedDocuments(report.relatedDocuments);
|
||||
}
|
||||
} else if (report.kind === 'unchanged') {
|
||||
// Unchanged report - diagnostics are still valid
|
||||
this._previousResultId = report.resultId;
|
||||
// No need to update markers
|
||||
}
|
||||
}
|
||||
|
||||
private _handleRelatedDocuments(relatedDocuments: { [key: string]: any }): void {
|
||||
for (const [uri, report] of Object.entries(relatedDocuments)) {
|
||||
try {
|
||||
const translated = this._connection.bridge.translateBack({ uri }, { line: 0, character: 0 });
|
||||
const model = translated.textModel;
|
||||
|
||||
if (!model || model.isDisposed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (report.kind === 'full') {
|
||||
const markers = report.items.map((diagnostic: Diagnostic) => toDiagnosticMarker(diagnostic));
|
||||
monaco.editor.setModelMarkers(model, this._markerOwner, markers);
|
||||
}
|
||||
} catch (error) {
|
||||
// Model not found - this is normal
|
||||
console.debug(`Could not set related diagnostics for ${uri}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
if (this._updateHandle !== undefined) {
|
||||
clearTimeout(this._updateHandle);
|
||||
this._updateHandle = undefined;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DocumentHighlightRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toMonacoDocumentHighlightKind } from './common';
|
||||
|
||||
export class LspDocumentHighlightFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
documentHighlight: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentDocumentHighlight, true, capability => {
|
||||
return monaco.languages.registerDocumentHighlightProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDocumentHighlightProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDocumentHighlightProvider implements monaco.languages.DocumentHighlightProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DocumentHighlightRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideDocumentHighlights(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.DocumentHighlight[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentDocumentHighlight({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(highlight => ({
|
||||
range: this._client.bridge.translateBackRange(translated.textDocument, highlight.range).range,
|
||||
kind: toMonacoDocumentHighlightKind(highlight.kind),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DocumentLinkRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspDocumentLinkFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
documentLink: {
|
||||
dynamicRegistration: true,
|
||||
tooltipSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentDocumentLink, true, capability => {
|
||||
return monaco.languages.registerLinkProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDocumentLinkProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDocumentLinkProvider implements monaco.languages.LinkProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DocumentLinkRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideLinks(
|
||||
model: monaco.editor.ITextModel,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.ILinksList | null> {
|
||||
const translated = this._client.bridge.translate(model, new monaco.Position(1, 1));
|
||||
|
||||
const result = await this._client.server.textDocumentDocumentLink({
|
||||
textDocument: translated.textDocument,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
links: result.map(link => ({
|
||||
range: this._client.bridge.translateBackRange(translated.textDocument, link.range).range,
|
||||
url: link.target,
|
||||
tooltip: link.tooltip,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async resolveLink(
|
||||
link: monaco.languages.ILink,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.ILink> {
|
||||
if (!this._capabilities.resolveProvider) {
|
||||
return link;
|
||||
}
|
||||
|
||||
// TODO: Implement resolve
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DocumentSymbolRegistrationOptions, DocumentSymbol, SymbolInformation, SymbolTag } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { lspSymbolKindToMonacoSymbolKind, toMonacoSymbolKind, toMonacoSymbolTag } from './common';
|
||||
|
||||
export class LspDocumentSymbolFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
documentSymbol: {
|
||||
dynamicRegistration: true,
|
||||
hierarchicalDocumentSymbolSupport: true,
|
||||
symbolKind: {
|
||||
valueSet: Array.from(lspSymbolKindToMonacoSymbolKind.keys()),
|
||||
},
|
||||
tagSupport: {
|
||||
valueSet: [SymbolTag.Deprecated],
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentDocumentSymbol, true, capability => {
|
||||
return monaco.languages.registerDocumentSymbolProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDocumentSymbolProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDocumentSymbolProvider implements monaco.languages.DocumentSymbolProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DocumentSymbolRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideDocumentSymbols(
|
||||
model: monaco.editor.ITextModel,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.DocumentSymbol[] | null> {
|
||||
const translated = this._client.bridge.translate(model, new monaco.Position(1, 1));
|
||||
|
||||
const result = await this._client.server.textDocumentDocumentSymbol({
|
||||
textDocument: translated.textDocument,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(result) && result.length > 0) {
|
||||
if ('location' in result[0]) {
|
||||
// SymbolInformation[]
|
||||
return (result as SymbolInformation[]).map(symbol => toMonacoSymbolInformation(symbol, this._client));
|
||||
} else {
|
||||
// DocumentSymbol[]
|
||||
return (result as DocumentSymbol[]).map(symbol => toMonacoDocumentSymbol(symbol, this._client, translated.textDocument));
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function toMonacoDocumentSymbol(
|
||||
symbol: DocumentSymbol,
|
||||
client: LspConnection,
|
||||
textDocument: { uri: string }
|
||||
): monaco.languages.DocumentSymbol {
|
||||
return {
|
||||
name: symbol.name,
|
||||
detail: symbol.detail || '',
|
||||
kind: toMonacoSymbolKind(symbol.kind),
|
||||
tags: symbol.tags?.map(tag => toMonacoSymbolTag(tag)).filter((t): t is monaco.languages.SymbolTag => t !== undefined) || [],
|
||||
range: client.bridge.translateBackRange(textDocument, symbol.range).range,
|
||||
selectionRange: client.bridge.translateBackRange(textDocument, symbol.selectionRange).range,
|
||||
children: symbol.children?.map(child => toMonacoDocumentSymbol(child, client, textDocument)) || [],
|
||||
};
|
||||
}
|
||||
|
||||
function toMonacoSymbolInformation(
|
||||
symbol: SymbolInformation,
|
||||
client: LspConnection
|
||||
): monaco.languages.DocumentSymbol {
|
||||
return {
|
||||
name: symbol.name,
|
||||
detail: '',
|
||||
kind: toMonacoSymbolKind(symbol.kind),
|
||||
tags: symbol.tags?.map(tag => toMonacoSymbolTag(tag)).filter((t): t is monaco.languages.SymbolTag => t !== undefined) || [],
|
||||
range: client.bridge.translateBackRange({ uri: symbol.location.uri }, symbol.location.range).range,
|
||||
selectionRange: client.bridge.translateBackRange({ uri: symbol.location.uri }, symbol.location.range).range,
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, FoldingRangeRegistrationOptions, FoldingRangeKind } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toMonacoFoldingRangeKind } from './common';
|
||||
|
||||
export class LspFoldingRangeFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
foldingRange: {
|
||||
dynamicRegistration: true,
|
||||
rangeLimit: 5000,
|
||||
lineFoldingOnly: false,
|
||||
foldingRangeKind: {
|
||||
valueSet: [FoldingRangeKind.Comment, FoldingRangeKind.Imports, FoldingRangeKind.Region],
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentFoldingRange, true, capability => {
|
||||
return monaco.languages.registerFoldingRangeProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspFoldingRangeProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspFoldingRangeProvider implements monaco.languages.FoldingRangeProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: FoldingRangeRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideFoldingRanges(
|
||||
model: monaco.editor.ITextModel,
|
||||
context: monaco.languages.FoldingContext,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.FoldingRange[] | null> {
|
||||
const translated = this._client.bridge.translate(model, new monaco.Position(1, 1));
|
||||
|
||||
const result = await this._client.server.textDocumentFoldingRange({
|
||||
textDocument: translated.textDocument,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(range => ({
|
||||
start: range.startLine + 1,
|
||||
end: range.endLine + 1,
|
||||
kind: toMonacoFoldingRangeKind(range.kind),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DocumentFormattingRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspFormattingFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
formatting: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentFormatting, true, capability => {
|
||||
return monaco.languages.registerDocumentFormattingEditProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDocumentFormattingProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDocumentFormattingProvider implements monaco.languages.DocumentFormattingEditProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DocumentFormattingRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideDocumentFormattingEdits(
|
||||
model: monaco.editor.ITextModel,
|
||||
options: monaco.languages.FormattingOptions,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.TextEdit[] | null> {
|
||||
const translated = this._client.bridge.translate(model, new monaco.Position(1, 1));
|
||||
|
||||
const result = await this._client.server.textDocumentFormatting({
|
||||
textDocument: translated.textDocument,
|
||||
options: {
|
||||
tabSize: options.tabSize,
|
||||
insertSpaces: options.insertSpaces,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(edit => ({
|
||||
range: this._client.bridge.translateBackRange(translated.textDocument, edit.range).range,
|
||||
text: edit.newText,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, HoverRegistrationOptions, MarkupContent, MarkedString, MarkupKind } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspHoverFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
hover: {
|
||||
dynamicRegistration: true,
|
||||
contentFormat: [MarkupKind.Markdown, MarkupKind.PlainText],
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentHover, true, capability => {
|
||||
return monaco.languages.registerHoverProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspHoverProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspHoverProvider implements monaco.languages.HoverProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: HoverRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideHover(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.Hover | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentHover({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result || !result.contents) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
contents: toMonacoMarkdownString(result.contents),
|
||||
range: result.range ? this._client.bridge.translateBackRange(translated.textDocument, result.range).range : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function toMonacoMarkdownString(
|
||||
contents: MarkupContent | MarkedString | MarkedString[]
|
||||
): monaco.IMarkdownString[] {
|
||||
if (Array.isArray(contents)) {
|
||||
return contents.map(c => toSingleMarkdownString(c));
|
||||
}
|
||||
return [toSingleMarkdownString(contents)];
|
||||
}
|
||||
|
||||
function toSingleMarkdownString(content: MarkupContent | MarkedString): monaco.IMarkdownString {
|
||||
if (typeof content === 'string') {
|
||||
return { value: content, isTrusted: true };
|
||||
}
|
||||
if ('kind' in content) {
|
||||
// MarkupContent
|
||||
return { value: content.value, isTrusted: true };
|
||||
}
|
||||
// MarkedString with language
|
||||
return { value: `\`\`\`${content.language}\n${content.value}\n\`\`\``, isTrusted: true };
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, ImplementationRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toMonacoLocation } from "./common";
|
||||
|
||||
export class LspImplementationFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
implementation: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentImplementation, true, capability => {
|
||||
return monaco.languages.registerImplementationProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspImplementationProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspImplementationProvider implements monaco.languages.ImplementationProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: ImplementationRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideImplementation(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.Definition | monaco.languages.LocationLink[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentImplementation({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return result.map(loc => toMonacoLocation(loc, this._client));
|
||||
}
|
||||
|
||||
return toMonacoLocation(result, this._client);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, InlayHintRegistrationOptions, InlayHint, MarkupContent, api } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { assertTargetTextModel } from '../ITextModelBridge';
|
||||
import { toMonacoCommand, toMonacoInlayHintKind } from './common';
|
||||
|
||||
export class LspInlayHintsFeature extends Disposable {
|
||||
private readonly _providers = new Set<LspInlayHintsProvider>();
|
||||
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
inlayHint: {
|
||||
dynamicRegistration: true,
|
||||
resolveSupport: {
|
||||
properties: ['tooltip', 'textEdits', 'label.tooltip', 'label.location', 'label.command'],
|
||||
},
|
||||
}
|
||||
},
|
||||
workspace: {
|
||||
inlayHint: {
|
||||
refreshSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.connection.registerRequestHandler(api.client.workspaceInlayHintRefresh, async () => {
|
||||
// Fire onDidChangeInlayHints for all providers
|
||||
for (const provider of this._providers) {
|
||||
provider.refresh();
|
||||
}
|
||||
return { ok: null };
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentInlayHint, true, capability => {
|
||||
const provider = new LspInlayHintsProvider(this._connection, capability);
|
||||
this._providers.add(provider);
|
||||
|
||||
const disposable = monaco.languages.registerInlayHintsProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
provider,
|
||||
);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._providers.delete(provider);
|
||||
disposable.dispose();
|
||||
}
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtendedInlayHint extends monaco.languages.InlayHint {
|
||||
_lspInlayHint: InlayHint;
|
||||
_targetUri: string;
|
||||
}
|
||||
|
||||
class LspInlayHintsProvider implements monaco.languages.InlayHintsProvider {
|
||||
private readonly _onDidChangeInlayHints = new monaco.Emitter<void>();
|
||||
public readonly onDidChangeInlayHints = this._onDidChangeInlayHints.event;
|
||||
|
||||
public readonly resolveInlayHint;
|
||||
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: InlayHintRegistrationOptions,
|
||||
) {
|
||||
if (_capabilities.resolveProvider) {
|
||||
this.resolveInlayHint = async (hint: ExtendedInlayHint, token: monaco.CancellationToken): Promise<monaco.languages.InlayHint> => {
|
||||
|
||||
const resolved = await this._client.server.inlayHintResolve(hint._lspInlayHint);
|
||||
|
||||
if (resolved.tooltip) {
|
||||
hint.tooltip = toMonacoTooltip(resolved.tooltip);
|
||||
}
|
||||
|
||||
if (resolved.label !== hint._lspInlayHint.label) {
|
||||
hint.label = toLspInlayHintLabel(resolved.label);
|
||||
}
|
||||
|
||||
if (resolved.textEdits) {
|
||||
hint.textEdits = resolved.textEdits.map(edit => {
|
||||
const translated = this._client.bridge.translateBackRange(
|
||||
{ uri: hint._targetUri },
|
||||
edit.range
|
||||
);
|
||||
return {
|
||||
range: translated.range,
|
||||
text: edit.newText,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return hint;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this._onDidChangeInlayHints.fire();
|
||||
}
|
||||
|
||||
async provideInlayHints(
|
||||
model: monaco.editor.ITextModel,
|
||||
range: monaco.Range,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.InlayHintList | null> {
|
||||
const translated = this._client.bridge.translate(model, range.getStartPosition());
|
||||
|
||||
const result = await retryOnContentModified(async () =>
|
||||
await this._client.server.textDocumentInlayHint({
|
||||
textDocument: translated.textDocument,
|
||||
range: this._client.bridge.translateRange(model, range),
|
||||
})
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
hints: result.map(hint => {
|
||||
const monacoHint: ExtendedInlayHint = {
|
||||
label: toLspInlayHintLabel(hint.label),
|
||||
position: assertTargetTextModel(
|
||||
this._client.bridge.translateBack(translated.textDocument, hint.position),
|
||||
model
|
||||
).position,
|
||||
kind: toMonacoInlayHintKind(hint.kind),
|
||||
tooltip: toMonacoTooltip(hint.tooltip),
|
||||
paddingLeft: hint.paddingLeft,
|
||||
paddingRight: hint.paddingRight,
|
||||
textEdits: hint.textEdits?.map(edit => ({
|
||||
range: assertTargetTextModel(
|
||||
this._client.bridge.translateBackRange(translated.textDocument, edit.range),
|
||||
model
|
||||
).range,
|
||||
text: edit.newText,
|
||||
})),
|
||||
_lspInlayHint: hint,
|
||||
_targetUri: translated.textDocument.uri,
|
||||
};
|
||||
return monacoHint;
|
||||
}),
|
||||
dispose: () => { },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function retryOnContentModified<T>(cb: () => Promise<T>): Promise<T> {
|
||||
const nRetries = 3;
|
||||
for (let triesLeft = nRetries; ; triesLeft--) {
|
||||
try {
|
||||
return await cb();
|
||||
} catch (e: any) {
|
||||
if (e.message === 'content modified' && triesLeft > 0) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toLspInlayHintLabel(label: string | any[]): string | monaco.languages.InlayHintLabelPart[] {
|
||||
if (typeof label === 'string') {
|
||||
return label;
|
||||
}
|
||||
|
||||
return label.map(part => {
|
||||
const monacoLabelPart: monaco.languages.InlayHintLabelPart = {
|
||||
label: part.value,
|
||||
tooltip: toMonacoTooltip(part.tooltip),
|
||||
command: toMonacoCommand(part.command),
|
||||
};
|
||||
|
||||
if (part.location) {
|
||||
monacoLabelPart.location = {
|
||||
uri: monaco.Uri.parse(part.location.uri),
|
||||
range: new monaco.Range(
|
||||
part.location.range.start.line + 1,
|
||||
part.location.range.start.character + 1,
|
||||
part.location.range.end.line + 1,
|
||||
part.location.range.end.character + 1
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return monacoLabelPart;
|
||||
});
|
||||
}
|
||||
|
||||
function toMonacoTooltip(tooltip: string | MarkupContent | undefined): string | monaco.IMarkdownString | undefined {
|
||||
if (!tooltip) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof tooltip === 'string') {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
return {
|
||||
value: tooltip.value,
|
||||
isTrusted: true,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DocumentOnTypeFormattingRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspOnTypeFormattingFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
onTypeFormatting: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentOnTypeFormatting, true, capability => {
|
||||
return monaco.languages.registerOnTypeFormattingEditProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspOnTypeFormattingProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspOnTypeFormattingProvider implements monaco.languages.OnTypeFormattingEditProvider {
|
||||
public readonly autoFormatTriggerCharacters: string[];
|
||||
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DocumentOnTypeFormattingRegistrationOptions,
|
||||
) {
|
||||
this.autoFormatTriggerCharacters = [
|
||||
_capabilities.firstTriggerCharacter,
|
||||
...(_capabilities.moreTriggerCharacter || [])
|
||||
];
|
||||
}
|
||||
|
||||
async provideOnTypeFormattingEdits(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
ch: string,
|
||||
options: monaco.languages.FormattingOptions,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.TextEdit[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentOnTypeFormatting({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
ch,
|
||||
options: {
|
||||
tabSize: options.tabSize,
|
||||
insertSpaces: options.insertSpaces,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(edit => ({
|
||||
range: this._client.bridge.translateBackRange(translated.textDocument, edit.range).range,
|
||||
text: edit.newText,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, DocumentRangeFormattingRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspRangeFormattingFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
rangeFormatting: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentRangeFormatting, true, capability => {
|
||||
return monaco.languages.registerDocumentRangeFormattingEditProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspDocumentRangeFormattingProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspDocumentRangeFormattingProvider implements monaco.languages.DocumentRangeFormattingEditProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: DocumentRangeFormattingRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideDocumentRangeFormattingEdits(
|
||||
model: monaco.editor.ITextModel,
|
||||
range: monaco.Range,
|
||||
options: monaco.languages.FormattingOptions,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.TextEdit[] | null> {
|
||||
const translated = this._client.bridge.translate(model, range.getStartPosition());
|
||||
|
||||
const result = await this._client.server.textDocumentRangeFormatting({
|
||||
textDocument: translated.textDocument,
|
||||
range: this._client.bridge.translateRange(model, range),
|
||||
options: {
|
||||
tabSize: options.tabSize,
|
||||
insertSpaces: options.insertSpaces,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(edit => ({
|
||||
range: this._client.bridge.translateBackRange(translated.textDocument, edit.range).range,
|
||||
text: edit.newText,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, ReferenceRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspReferencesFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
references: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentReferences, true, capability => {
|
||||
return monaco.languages.registerReferenceProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspReferenceProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspReferenceProvider implements monaco.languages.ReferenceProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: ReferenceRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideReferences(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
context: monaco.languages.ReferenceContext,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.Location[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentReferences({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
context: {
|
||||
includeDeclaration: context.includeDeclaration,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(loc => {
|
||||
const translated = this._client.bridge.translateBackRange({ uri: loc.uri }, loc.range);
|
||||
return {
|
||||
uri: translated.textModel.uri,
|
||||
range: translated.range,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, RenameRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspRenameFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
rename: {
|
||||
dynamicRegistration: true,
|
||||
prepareSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentRename, true, capability => {
|
||||
return monaco.languages.registerRenameProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspRenameProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspRenameProvider implements monaco.languages.RenameProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: RenameRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideRenameEdits(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
newName: string,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.WorkspaceEdit | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentRename({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
newName,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toMonacoWorkspaceEdit(result, this._client);
|
||||
}
|
||||
|
||||
async resolveRenameLocation(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.RenameLocation | null> {
|
||||
if (!this._capabilities.prepareProvider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentPrepareRename({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('range' in result && 'placeholder' in result) {
|
||||
return {
|
||||
range: this._client.bridge.translateBackRange(translated.textDocument, result.range).range,
|
||||
text: result.placeholder,
|
||||
};
|
||||
} else if ('defaultBehavior' in result) {
|
||||
return null;
|
||||
} else if ('start' in result && 'end' in result) {
|
||||
const range = this._client.bridge.translateBackRange(translated.textDocument, result).range;
|
||||
return {
|
||||
range,
|
||||
text: model.getValueInRange(range),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function toMonacoWorkspaceEdit(
|
||||
edit: any,
|
||||
client: LspConnection
|
||||
): monaco.languages.WorkspaceEdit {
|
||||
const edits: monaco.languages.IWorkspaceTextEdit[] = [];
|
||||
|
||||
if (edit.changes) {
|
||||
for (const uri in edit.changes) {
|
||||
const textEdits = edit.changes[uri];
|
||||
for (const textEdit of textEdits) {
|
||||
const translated = client.bridge.translateBackRange({ uri }, textEdit.range);
|
||||
edits.push({
|
||||
resource: translated.textModel.uri,
|
||||
versionId: undefined,
|
||||
textEdit: {
|
||||
range: translated.range,
|
||||
text: textEdit.newText,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edit.documentChanges) {
|
||||
for (const change of edit.documentChanges) {
|
||||
if ('textDocument' in change) {
|
||||
// TextDocumentEdit
|
||||
const uri = change.textDocument.uri;
|
||||
for (const textEdit of change.edits) {
|
||||
const translated = client.bridge.translateBackRange({ uri }, textEdit.range);
|
||||
edits.push({
|
||||
resource: translated.textModel.uri,
|
||||
versionId: change.textDocument.version,
|
||||
textEdit: {
|
||||
range: translated.range,
|
||||
text: textEdit.newText,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// TODO: Handle CreateFile, RenameFile, DeleteFile
|
||||
}
|
||||
}
|
||||
|
||||
return { edits };
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, SelectionRangeRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspSelectionRangeFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
selectionRange: {
|
||||
dynamicRegistration: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentSelectionRange, true, capability => {
|
||||
return monaco.languages.registerSelectionRangeProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspSelectionRangeProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspSelectionRangeProvider implements monaco.languages.SelectionRangeProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: SelectionRangeRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideSelectionRanges(
|
||||
model: monaco.editor.ITextModel,
|
||||
positions: monaco.Position[],
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.SelectionRange[][] | null> {
|
||||
const translated = this._client.bridge.translate(model, positions[0]);
|
||||
|
||||
const result = await this._client.server.textDocumentSelectionRange({
|
||||
textDocument: translated.textDocument,
|
||||
positions: positions.map(pos => this._client.bridge.translate(model, pos).position),
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.map(selRange => this.convertSelectionRange(selRange, translated.textDocument));
|
||||
}
|
||||
|
||||
private convertSelectionRange(
|
||||
range: any,
|
||||
textDocument: { uri: string }
|
||||
): monaco.languages.SelectionRange[] {
|
||||
const result: monaco.languages.SelectionRange[] = [];
|
||||
let current = range;
|
||||
|
||||
while (current) {
|
||||
result.push({
|
||||
range: this._client.bridge.translateBackRange(textDocument, current.range).range,
|
||||
});
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, SemanticTokensRegistrationOptions, TokenFormat } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
|
||||
export class LspSemanticTokensFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
semanticTokens: {
|
||||
dynamicRegistration: true,
|
||||
requests: {
|
||||
range: true,
|
||||
full: {
|
||||
delta: true,
|
||||
},
|
||||
},
|
||||
tokenTypes: [
|
||||
'namespace', 'type', 'class', 'enum', 'interface', 'struct',
|
||||
'typeParameter', 'parameter', 'variable', 'property', 'enumMember',
|
||||
'event', 'function', 'method', 'macro', 'keyword', 'modifier',
|
||||
'comment', 'string', 'number', 'regexp', 'operator', 'decorator'
|
||||
],
|
||||
tokenModifiers: [
|
||||
'declaration', 'definition', 'readonly', 'static', 'deprecated',
|
||||
'abstract', 'async', 'modification', 'documentation', 'defaultLibrary'
|
||||
],
|
||||
formats: [TokenFormat.Relative],
|
||||
overlappingTokenSupport: false,
|
||||
multilineTokenSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentSemanticTokensFull, true, capability => {
|
||||
return monaco.languages.registerDocumentSemanticTokensProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspSemanticTokensProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspSemanticTokensProvider implements monaco.languages.DocumentSemanticTokensProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: SemanticTokensRegistrationOptions,
|
||||
) { }
|
||||
|
||||
getLegend(): monaco.languages.SemanticTokensLegend {
|
||||
return {
|
||||
tokenTypes: this._capabilities.legend.tokenTypes,
|
||||
tokenModifiers: this._capabilities.legend.tokenModifiers,
|
||||
};
|
||||
}
|
||||
|
||||
releaseDocumentSemanticTokens(resultId: string | undefined): void {
|
||||
// Monaco will call this when it's done with a result
|
||||
// We can potentially notify the server if needed
|
||||
}
|
||||
|
||||
async provideDocumentSemanticTokens(
|
||||
model: monaco.editor.ITextModel,
|
||||
lastResultId: string | null,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.SemanticTokens | monaco.languages.SemanticTokensEdits | null> {
|
||||
const translated = this._client.bridge.translate(model, model.getPositionAt(0));
|
||||
|
||||
// Try delta request if we have a previous result and server supports it
|
||||
const full = this._capabilities.full;
|
||||
if (lastResultId && full && typeof full === 'object' && full.delta) {
|
||||
const deltaResult = await this._client.server.textDocumentSemanticTokensFullDelta({
|
||||
textDocument: translated.textDocument,
|
||||
previousResultId: lastResultId,
|
||||
});
|
||||
|
||||
if (!deltaResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if it's a delta or full result
|
||||
if ('edits' in deltaResult) {
|
||||
// It's a delta
|
||||
return {
|
||||
resultId: deltaResult.resultId,
|
||||
edits: deltaResult.edits.map(edit => ({
|
||||
start: edit.start,
|
||||
deleteCount: edit.deleteCount,
|
||||
data: edit.data ? new Uint32Array(edit.data) : undefined,
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
// It's a full result
|
||||
return {
|
||||
resultId: deltaResult.resultId,
|
||||
data: new Uint32Array(deltaResult.data),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Full request
|
||||
const result = await this._client.server.textDocumentSemanticTokensFull({
|
||||
textDocument: translated.textDocument,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
resultId: result.resultId,
|
||||
data: new Uint32Array(result.data),
|
||||
};
|
||||
}
|
||||
|
||||
async provideDocumentSemanticTokensEdits?(
|
||||
model: monaco.editor.ITextModel,
|
||||
previousResultId: string,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.SemanticTokens | monaco.languages.SemanticTokensEdits | null> {
|
||||
// This method is called when Monaco wants to use delta updates
|
||||
// We can delegate to provideDocumentSemanticTokens which handles both
|
||||
return this.provideDocumentSemanticTokens(model, previousResultId, token);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, SignatureHelpRegistrationOptions, MarkupContent, MarkupKind } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toLspSignatureHelpTriggerKind } from './common';
|
||||
|
||||
export class LspSignatureHelpFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
signatureHelp: {
|
||||
dynamicRegistration: true,
|
||||
contextSupport: true,
|
||||
signatureInformation: {
|
||||
documentationFormat: [MarkupKind.Markdown, MarkupKind.PlainText],
|
||||
parameterInformation: {
|
||||
labelOffsetSupport: true,
|
||||
},
|
||||
activeParameterSupport: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentSignatureHelp, true, capability => {
|
||||
return monaco.languages.registerSignatureHelpProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspSignatureHelpProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspSignatureHelpProvider implements monaco.languages.SignatureHelpProvider {
|
||||
public readonly signatureHelpTriggerCharacters?: readonly string[];
|
||||
public readonly signatureHelpRetriggerCharacters?: readonly string[];
|
||||
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: SignatureHelpRegistrationOptions,
|
||||
) {
|
||||
this.signatureHelpTriggerCharacters = _capabilities.triggerCharacters;
|
||||
this.signatureHelpRetriggerCharacters = _capabilities.retriggerCharacters;
|
||||
}
|
||||
|
||||
async provideSignatureHelp(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken,
|
||||
context: monaco.languages.SignatureHelpContext
|
||||
): Promise<monaco.languages.SignatureHelpResult | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentSignatureHelp({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
context: {
|
||||
triggerKind: toLspSignatureHelpTriggerKind(context.triggerKind),
|
||||
triggerCharacter: context.triggerCharacter,
|
||||
isRetrigger: context.isRetrigger,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
signatures: result.signatures.map(sig => ({
|
||||
label: sig.label,
|
||||
documentation: toMonacoDocumentation(sig.documentation),
|
||||
parameters: sig.parameters?.map(param => ({
|
||||
label: param.label,
|
||||
documentation: toMonacoDocumentation(param.documentation),
|
||||
})) || [],
|
||||
activeParameter: sig.activeParameter,
|
||||
})),
|
||||
activeSignature: result.activeSignature || 0,
|
||||
activeParameter: result.activeParameter || 0,
|
||||
},
|
||||
dispose: () => { },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function toMonacoDocumentation(
|
||||
doc: string | MarkupContent | undefined
|
||||
): string | monaco.IMarkdownString | undefined {
|
||||
if (!doc) return undefined;
|
||||
if (typeof doc === 'string') return doc;
|
||||
return {
|
||||
value: doc.value,
|
||||
isTrusted: true,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import { capabilities, TypeDefinitionRegistrationOptions } from '../../../src/types';
|
||||
import { Disposable } from '../../utils';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
import { toMonacoLanguageSelector } from './common';
|
||||
import { toMonacoLocation } from "./common";
|
||||
|
||||
export class LspTypeDefinitionFeature extends Disposable {
|
||||
constructor(
|
||||
private readonly _connection: LspConnection,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._connection.capabilities.addStaticClientCapabilities({
|
||||
textDocument: {
|
||||
typeDefinition: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connection.capabilities.registerCapabilityHandler(capabilities.textDocumentTypeDefinition, true, capability => {
|
||||
return monaco.languages.registerTypeDefinitionProvider(
|
||||
toMonacoLanguageSelector(capability.documentSelector),
|
||||
new LspTypeDefinitionProvider(this._connection, capability),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LspTypeDefinitionProvider implements monaco.languages.TypeDefinitionProvider {
|
||||
constructor(
|
||||
private readonly _client: LspConnection,
|
||||
private readonly _capabilities: TypeDefinitionRegistrationOptions,
|
||||
) { }
|
||||
|
||||
async provideTypeDefinition(
|
||||
model: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.Definition | monaco.languages.LocationLink[] | null> {
|
||||
const translated = this._client.bridge.translate(model, position);
|
||||
|
||||
const result = await this._client.server.textDocumentTypeDefinition({
|
||||
textDocument: translated.textDocument,
|
||||
position: translated.position,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return result.map(loc => toMonacoLocation(loc, this._client));
|
||||
}
|
||||
|
||||
return toMonacoLocation(result, this._client);
|
||||
}
|
||||
}
|
||||
401
monaco-lsp-client/src/adapters/languageFeatures/common.ts
Normal file
401
monaco-lsp-client/src/adapters/languageFeatures/common.ts
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
import * as monaco from 'monaco-editor-core';
|
||||
import {
|
||||
CodeActionKind,
|
||||
CodeActionTriggerKind,
|
||||
Command,
|
||||
CompletionItemKind,
|
||||
CompletionItemTag,
|
||||
CompletionTriggerKind,
|
||||
Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
DiagnosticTag,
|
||||
DocumentHighlightKind,
|
||||
DocumentSelector,
|
||||
FoldingRangeKind,
|
||||
InlayHintKind,
|
||||
InsertTextFormat,
|
||||
Location,
|
||||
LocationLink,
|
||||
SignatureHelpTriggerKind,
|
||||
SymbolKind,
|
||||
SymbolTag,
|
||||
} from '../../../src/types';
|
||||
import { LspConnection } from '../LspConnection';
|
||||
|
||||
// ============================================================================
|
||||
// Code Action Kind
|
||||
// ============================================================================
|
||||
|
||||
export const lspCodeActionKindToMonacoCodeActionKind = new Map<CodeActionKind, string>([
|
||||
[CodeActionKind.Empty, ''],
|
||||
[CodeActionKind.QuickFix, 'quickfix'],
|
||||
[CodeActionKind.Refactor, 'refactor'],
|
||||
[CodeActionKind.RefactorExtract, 'refactor.extract'],
|
||||
[CodeActionKind.RefactorInline, 'refactor.inline'],
|
||||
[CodeActionKind.RefactorRewrite, 'refactor.rewrite'],
|
||||
[CodeActionKind.Source, 'source'],
|
||||
[CodeActionKind.SourceOrganizeImports, 'source.organizeImports'],
|
||||
[CodeActionKind.SourceFixAll, 'source.fixAll'],
|
||||
]);
|
||||
|
||||
export function toMonacoCodeActionKind(kind: CodeActionKind | undefined): string | undefined {
|
||||
if (!kind) {
|
||||
return undefined;
|
||||
}
|
||||
return lspCodeActionKindToMonacoCodeActionKind.get(kind) ?? kind;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Code Action Trigger Kind
|
||||
// ============================================================================
|
||||
|
||||
export const monacoCodeActionTriggerTypeToLspCodeActionTriggerKind = new Map<monaco.languages.CodeActionTriggerType, CodeActionTriggerKind>([
|
||||
[monaco.languages.CodeActionTriggerType.Invoke, CodeActionTriggerKind.Invoked],
|
||||
[monaco.languages.CodeActionTriggerType.Auto, CodeActionTriggerKind.Automatic],
|
||||
]);
|
||||
|
||||
export function toLspCodeActionTriggerKind(monacoTrigger: monaco.languages.CodeActionTriggerType): CodeActionTriggerKind {
|
||||
return monacoCodeActionTriggerTypeToLspCodeActionTriggerKind.get(monacoTrigger) ?? CodeActionTriggerKind.Invoked;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Completion Item Kind
|
||||
// ============================================================================
|
||||
|
||||
export const lspCompletionItemKindToMonacoCompletionItemKind = new Map<CompletionItemKind, monaco.languages.CompletionItemKind>([
|
||||
[CompletionItemKind.Text, monaco.languages.CompletionItemKind.Text],
|
||||
[CompletionItemKind.Method, monaco.languages.CompletionItemKind.Method],
|
||||
[CompletionItemKind.Function, monaco.languages.CompletionItemKind.Function],
|
||||
[CompletionItemKind.Constructor, monaco.languages.CompletionItemKind.Constructor],
|
||||
[CompletionItemKind.Field, monaco.languages.CompletionItemKind.Field],
|
||||
[CompletionItemKind.Variable, monaco.languages.CompletionItemKind.Variable],
|
||||
[CompletionItemKind.Class, monaco.languages.CompletionItemKind.Class],
|
||||
[CompletionItemKind.Interface, monaco.languages.CompletionItemKind.Interface],
|
||||
[CompletionItemKind.Module, monaco.languages.CompletionItemKind.Module],
|
||||
[CompletionItemKind.Property, monaco.languages.CompletionItemKind.Property],
|
||||
[CompletionItemKind.Unit, monaco.languages.CompletionItemKind.Unit],
|
||||
[CompletionItemKind.Value, monaco.languages.CompletionItemKind.Value],
|
||||
[CompletionItemKind.Enum, monaco.languages.CompletionItemKind.Enum],
|
||||
[CompletionItemKind.Keyword, monaco.languages.CompletionItemKind.Keyword],
|
||||
[CompletionItemKind.Snippet, monaco.languages.CompletionItemKind.Snippet],
|
||||
[CompletionItemKind.Color, monaco.languages.CompletionItemKind.Color],
|
||||
[CompletionItemKind.File, monaco.languages.CompletionItemKind.File],
|
||||
[CompletionItemKind.Reference, monaco.languages.CompletionItemKind.Reference],
|
||||
[CompletionItemKind.Folder, monaco.languages.CompletionItemKind.Folder],
|
||||
[CompletionItemKind.EnumMember, monaco.languages.CompletionItemKind.EnumMember],
|
||||
[CompletionItemKind.Constant, monaco.languages.CompletionItemKind.Constant],
|
||||
[CompletionItemKind.Struct, monaco.languages.CompletionItemKind.Struct],
|
||||
[CompletionItemKind.Event, monaco.languages.CompletionItemKind.Event],
|
||||
[CompletionItemKind.Operator, monaco.languages.CompletionItemKind.Operator],
|
||||
[CompletionItemKind.TypeParameter, monaco.languages.CompletionItemKind.TypeParameter],
|
||||
]);
|
||||
|
||||
export function toMonacoCompletionItemKind(kind: CompletionItemKind | undefined): monaco.languages.CompletionItemKind {
|
||||
if (!kind) {
|
||||
return monaco.languages.CompletionItemKind.Text;
|
||||
}
|
||||
return lspCompletionItemKindToMonacoCompletionItemKind.get(kind) ?? monaco.languages.CompletionItemKind.Text;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Completion Item Tag
|
||||
// ============================================================================
|
||||
|
||||
export const lspCompletionItemTagToMonacoCompletionItemTag = new Map<CompletionItemTag, monaco.languages.CompletionItemTag>([
|
||||
[CompletionItemTag.Deprecated, monaco.languages.CompletionItemTag.Deprecated],
|
||||
]);
|
||||
|
||||
export function toMonacoCompletionItemTag(tag: CompletionItemTag): monaco.languages.CompletionItemTag | undefined {
|
||||
return lspCompletionItemTagToMonacoCompletionItemTag.get(tag);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Completion Trigger Kind
|
||||
// ============================================================================
|
||||
|
||||
export const monacoCompletionTriggerKindToLspCompletionTriggerKind = new Map<monaco.languages.CompletionTriggerKind, CompletionTriggerKind>([
|
||||
[monaco.languages.CompletionTriggerKind.Invoke, CompletionTriggerKind.Invoked],
|
||||
[monaco.languages.CompletionTriggerKind.TriggerCharacter, CompletionTriggerKind.TriggerCharacter],
|
||||
[monaco.languages.CompletionTriggerKind.TriggerForIncompleteCompletions, CompletionTriggerKind.TriggerForIncompleteCompletions],
|
||||
]);
|
||||
|
||||
export function toLspCompletionTriggerKind(monacoKind: monaco.languages.CompletionTriggerKind): CompletionTriggerKind {
|
||||
return monacoCompletionTriggerKindToLspCompletionTriggerKind.get(monacoKind) ?? CompletionTriggerKind.Invoked;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Insert Text Format
|
||||
// ============================================================================
|
||||
|
||||
export const lspInsertTextFormatToMonacoInsertTextRules = new Map<InsertTextFormat, monaco.languages.CompletionItemInsertTextRule>([
|
||||
[InsertTextFormat.Snippet, monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet],
|
||||
]);
|
||||
|
||||
export function toMonacoInsertTextRules(format: InsertTextFormat | undefined): monaco.languages.CompletionItemInsertTextRule | undefined {
|
||||
if (!format) {
|
||||
return undefined;
|
||||
}
|
||||
return lspInsertTextFormatToMonacoInsertTextRules.get(format);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Symbol Kind
|
||||
// ============================================================================
|
||||
|
||||
export const lspSymbolKindToMonacoSymbolKind = new Map<SymbolKind, monaco.languages.SymbolKind>([
|
||||
[SymbolKind.File, monaco.languages.SymbolKind.File],
|
||||
[SymbolKind.Module, monaco.languages.SymbolKind.Module],
|
||||
[SymbolKind.Namespace, monaco.languages.SymbolKind.Namespace],
|
||||
[SymbolKind.Package, monaco.languages.SymbolKind.Package],
|
||||
[SymbolKind.Class, monaco.languages.SymbolKind.Class],
|
||||
[SymbolKind.Method, monaco.languages.SymbolKind.Method],
|
||||
[SymbolKind.Property, monaco.languages.SymbolKind.Property],
|
||||
[SymbolKind.Field, monaco.languages.SymbolKind.Field],
|
||||
[SymbolKind.Constructor, monaco.languages.SymbolKind.Constructor],
|
||||
[SymbolKind.Enum, monaco.languages.SymbolKind.Enum],
|
||||
[SymbolKind.Interface, monaco.languages.SymbolKind.Interface],
|
||||
[SymbolKind.Function, monaco.languages.SymbolKind.Function],
|
||||
[SymbolKind.Variable, monaco.languages.SymbolKind.Variable],
|
||||
[SymbolKind.Constant, monaco.languages.SymbolKind.Constant],
|
||||
[SymbolKind.String, monaco.languages.SymbolKind.String],
|
||||
[SymbolKind.Number, monaco.languages.SymbolKind.Number],
|
||||
[SymbolKind.Boolean, monaco.languages.SymbolKind.Boolean],
|
||||
[SymbolKind.Array, monaco.languages.SymbolKind.Array],
|
||||
[SymbolKind.Object, monaco.languages.SymbolKind.Object],
|
||||
[SymbolKind.Key, monaco.languages.SymbolKind.Key],
|
||||
[SymbolKind.Null, monaco.languages.SymbolKind.Null],
|
||||
[SymbolKind.EnumMember, monaco.languages.SymbolKind.EnumMember],
|
||||
[SymbolKind.Struct, monaco.languages.SymbolKind.Struct],
|
||||
[SymbolKind.Event, monaco.languages.SymbolKind.Event],
|
||||
[SymbolKind.Operator, monaco.languages.SymbolKind.Operator],
|
||||
[SymbolKind.TypeParameter, monaco.languages.SymbolKind.TypeParameter],
|
||||
]);
|
||||
|
||||
export function toMonacoSymbolKind(kind: SymbolKind): monaco.languages.SymbolKind {
|
||||
return lspSymbolKindToMonacoSymbolKind.get(kind) ?? monaco.languages.SymbolKind.File;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Symbol Tag
|
||||
// ============================================================================
|
||||
|
||||
export const lspSymbolTagToMonacoSymbolTag = new Map<SymbolTag, monaco.languages.SymbolTag>([
|
||||
[SymbolTag.Deprecated, monaco.languages.SymbolTag.Deprecated],
|
||||
]);
|
||||
|
||||
export function toMonacoSymbolTag(tag: SymbolTag): monaco.languages.SymbolTag | undefined {
|
||||
return lspSymbolTagToMonacoSymbolTag.get(tag);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Document Highlight Kind
|
||||
// ============================================================================
|
||||
|
||||
export const lspDocumentHighlightKindToMonacoDocumentHighlightKind = new Map<DocumentHighlightKind, monaco.languages.DocumentHighlightKind>([
|
||||
[DocumentHighlightKind.Text, monaco.languages.DocumentHighlightKind.Text],
|
||||
[DocumentHighlightKind.Read, monaco.languages.DocumentHighlightKind.Read],
|
||||
[DocumentHighlightKind.Write, monaco.languages.DocumentHighlightKind.Write],
|
||||
]);
|
||||
|
||||
export function toMonacoDocumentHighlightKind(kind: DocumentHighlightKind | undefined): monaco.languages.DocumentHighlightKind {
|
||||
if (!kind) {
|
||||
return monaco.languages.DocumentHighlightKind.Text;
|
||||
}
|
||||
return lspDocumentHighlightKindToMonacoDocumentHighlightKind.get(kind) ?? monaco.languages.DocumentHighlightKind.Text;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Folding Range Kind
|
||||
// ============================================================================
|
||||
|
||||
export const lspFoldingRangeKindToMonacoFoldingRangeKind = new Map<FoldingRangeKind, monaco.languages.FoldingRangeKind>([
|
||||
[FoldingRangeKind.Comment, monaco.languages.FoldingRangeKind.Comment],
|
||||
[FoldingRangeKind.Imports, monaco.languages.FoldingRangeKind.Imports],
|
||||
[FoldingRangeKind.Region, monaco.languages.FoldingRangeKind.Region],
|
||||
]);
|
||||
|
||||
export function toMonacoFoldingRangeKind(kind: FoldingRangeKind | undefined): monaco.languages.FoldingRangeKind | undefined {
|
||||
if (!kind) {
|
||||
return undefined;
|
||||
}
|
||||
return lspFoldingRangeKindToMonacoFoldingRangeKind.get(kind);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Diagnostic Severity
|
||||
// ============================================================================
|
||||
|
||||
export const monacoMarkerSeverityToLspDiagnosticSeverity = new Map<monaco.MarkerSeverity, DiagnosticSeverity>([
|
||||
[monaco.MarkerSeverity.Error, DiagnosticSeverity.Error],
|
||||
[monaco.MarkerSeverity.Warning, DiagnosticSeverity.Warning],
|
||||
[monaco.MarkerSeverity.Info, DiagnosticSeverity.Information],
|
||||
[monaco.MarkerSeverity.Hint, DiagnosticSeverity.Hint],
|
||||
]);
|
||||
|
||||
export function toLspDiagnosticSeverity(severity: monaco.MarkerSeverity): DiagnosticSeverity {
|
||||
return monacoMarkerSeverityToLspDiagnosticSeverity.get(severity) ?? DiagnosticSeverity.Error;
|
||||
}
|
||||
|
||||
export const lspDiagnosticSeverityToMonacoMarkerSeverity = new Map<DiagnosticSeverity, monaco.MarkerSeverity>([
|
||||
[DiagnosticSeverity.Error, monaco.MarkerSeverity.Error],
|
||||
[DiagnosticSeverity.Warning, monaco.MarkerSeverity.Warning],
|
||||
[DiagnosticSeverity.Information, monaco.MarkerSeverity.Info],
|
||||
[DiagnosticSeverity.Hint, monaco.MarkerSeverity.Hint],
|
||||
]);
|
||||
|
||||
export function toMonacoDiagnosticSeverity(severity: DiagnosticSeverity | undefined): monaco.MarkerSeverity {
|
||||
if (!severity) {
|
||||
return monaco.MarkerSeverity.Error;
|
||||
}
|
||||
return lspDiagnosticSeverityToMonacoMarkerSeverity.get(severity) ?? monaco.MarkerSeverity.Error;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Diagnostic Tag
|
||||
// ============================================================================
|
||||
|
||||
export const lspDiagnosticTagToMonacoMarkerTag = new Map<DiagnosticTag, monaco.MarkerTag>([
|
||||
[DiagnosticTag.Unnecessary, monaco.MarkerTag.Unnecessary],
|
||||
[DiagnosticTag.Deprecated, monaco.MarkerTag.Deprecated],
|
||||
]);
|
||||
|
||||
export function toMonacoDiagnosticTag(tag: DiagnosticTag): monaco.MarkerTag | undefined {
|
||||
return lspDiagnosticTagToMonacoMarkerTag.get(tag);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Signature Help Trigger Kind
|
||||
// ============================================================================
|
||||
|
||||
export const monacoSignatureHelpTriggerKindToLspSignatureHelpTriggerKind = new Map<monaco.languages.SignatureHelpTriggerKind, SignatureHelpTriggerKind>([
|
||||
[monaco.languages.SignatureHelpTriggerKind.Invoke, SignatureHelpTriggerKind.Invoked],
|
||||
[monaco.languages.SignatureHelpTriggerKind.TriggerCharacter, SignatureHelpTriggerKind.TriggerCharacter],
|
||||
[monaco.languages.SignatureHelpTriggerKind.ContentChange, SignatureHelpTriggerKind.ContentChange],
|
||||
]);
|
||||
|
||||
export function toLspSignatureHelpTriggerKind(monacoKind: monaco.languages.SignatureHelpTriggerKind): SignatureHelpTriggerKind {
|
||||
return monacoSignatureHelpTriggerKindToLspSignatureHelpTriggerKind.get(monacoKind) ?? SignatureHelpTriggerKind.Invoked;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Command
|
||||
// ============================================================================
|
||||
|
||||
export function toMonacoCommand(command: Command | undefined): monaco.languages.Command | undefined {
|
||||
if (!command) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
id: command.command,
|
||||
title: command.title,
|
||||
arguments: command.arguments,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Inlay Hint Kind
|
||||
// ============================================================================
|
||||
|
||||
export const lspInlayHintKindToMonacoInlayHintKind = new Map<InlayHintKind, monaco.languages.InlayHintKind>([
|
||||
[InlayHintKind.Type, monaco.languages.InlayHintKind.Type],
|
||||
[InlayHintKind.Parameter, monaco.languages.InlayHintKind.Parameter],
|
||||
]);
|
||||
|
||||
export function toMonacoInlayHintKind(kind: InlayHintKind | undefined): monaco.languages.InlayHintKind {
|
||||
if (!kind) {
|
||||
return monaco.languages.InlayHintKind.Type;
|
||||
}
|
||||
return lspInlayHintKindToMonacoInlayHintKind.get(kind) ?? monaco.languages.InlayHintKind.Type;
|
||||
} export function toMonacoLocation(
|
||||
location: Location | LocationLink,
|
||||
client: LspConnection
|
||||
): monaco.languages.Location | monaco.languages.LocationLink {
|
||||
if ('targetUri' in location) {
|
||||
// LocationLink
|
||||
const translatedRange = client.bridge.translateBackRange({ uri: location.targetUri }, location.targetRange);
|
||||
return {
|
||||
uri: translatedRange.textModel.uri,
|
||||
range: translatedRange.range,
|
||||
originSelectionRange: location.originSelectionRange
|
||||
? client.bridge.translateBackRange({ uri: location.targetUri }, location.originSelectionRange).range
|
||||
: undefined,
|
||||
targetSelectionRange: location.targetSelectionRange
|
||||
? client.bridge.translateBackRange({ uri: location.targetUri }, location.targetSelectionRange).range
|
||||
: undefined,
|
||||
};
|
||||
} else {
|
||||
// Location
|
||||
const translatedRange = client.bridge.translateBackRange({ uri: location.uri }, location.range);
|
||||
return {
|
||||
uri: translatedRange.textModel.uri,
|
||||
range: translatedRange.range,
|
||||
};
|
||||
}
|
||||
}
|
||||
export function toMonacoLanguageSelector(s: DocumentSelector | null): monaco.languages.LanguageSelector {
|
||||
if (!s || s.length === 0) {
|
||||
return { language: '*' };
|
||||
}
|
||||
return s.map<monaco.languages.LanguageFilter>(s => {
|
||||
if ('notebook' in s) {
|
||||
if (typeof s.notebook === 'string') {
|
||||
return { notebookType: s.notebook, language: s.language };
|
||||
} else {
|
||||
return { notebookType: s.notebook.notebookType, language: s.language, pattern: s.notebook.pattern, scheme: s.notebook.scheme };
|
||||
}
|
||||
} else {
|
||||
return { language: s.language, pattern: s.pattern, scheme: s.scheme };
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
export function matchesDocumentSelector(model: monaco.editor.ITextModel, selector: DocumentSelector | null): boolean {
|
||||
if (!selector) {
|
||||
return true;
|
||||
}
|
||||
const languageId = model.getLanguageId();
|
||||
const uri = model.uri.toString(true);
|
||||
|
||||
if (!selector || selector.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const filter of selector) {
|
||||
if (filter.language && filter.language !== '*' && filter.language !== languageId) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
export function toDiagnosticMarker(diagnostic: Diagnostic): monaco.editor.IMarkerData {
|
||||
const marker: monaco.editor.IMarkerData = {
|
||||
severity: toMonacoDiagnosticSeverity(diagnostic.severity),
|
||||
startLineNumber: diagnostic.range.start.line + 1,
|
||||
startColumn: diagnostic.range.start.character + 1,
|
||||
endLineNumber: diagnostic.range.end.line + 1,
|
||||
endColumn: diagnostic.range.end.character + 1,
|
||||
message: diagnostic.message,
|
||||
source: diagnostic.source,
|
||||
code: typeof diagnostic.code === 'string' ? diagnostic.code : diagnostic.code?.toString(),
|
||||
};
|
||||
|
||||
if (diagnostic.tags) {
|
||||
marker.tags = diagnostic.tags.map(tag => toMonacoDiagnosticTag(tag)).filter((tag): tag is monaco.MarkerTag => tag !== undefined);
|
||||
}
|
||||
|
||||
if (diagnostic.relatedInformation) {
|
||||
marker.relatedInformation = diagnostic.relatedInformation.map(info => ({
|
||||
resource: monaco.Uri.parse(info.location.uri),
|
||||
startLineNumber: info.location.range.start.line + 1,
|
||||
startColumn: info.location.range.start.character + 1,
|
||||
endLineNumber: info.location.range.end.line + 1,
|
||||
endColumn: info.location.range.end.character + 1,
|
||||
message: info.message,
|
||||
}));
|
||||
}
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
5
monaco-lsp-client/src/index.ts
Normal file
5
monaco-lsp-client/src/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { MonacoLspClient } from './adapters/LspClient';
|
||||
import { WebSocketTransport } from '@hediet/json-rpc-websocket';
|
||||
import { createTransportToWorker, createTransportToIFrame } from '@hediet/json-rpc-browser';
|
||||
|
||||
export { MonacoLspClient, WebSocketTransport, createTransportToWorker, createTransportToIFrame };
|
||||
7514
monaco-lsp-client/src/types.ts
Normal file
7514
monaco-lsp-client/src/types.ts
Normal file
File diff suppressed because it is too large
Load diff
75
monaco-lsp-client/src/utils.ts
Normal file
75
monaco-lsp-client/src/utils.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class Disposable implements IDisposable {
|
||||
static None = Object.freeze<IDisposable>({ dispose() { } });
|
||||
|
||||
private _store = new DisposableStore();
|
||||
|
||||
constructor() { }
|
||||
|
||||
public dispose(): void {
|
||||
this._store.dispose();
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
if ((t as any) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
return this._store.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
export class DisposableStore implements IDisposable {
|
||||
static DISABLE_DISPOSED_WARNING = false;
|
||||
|
||||
private _toDispose = new Set<IDisposable>();
|
||||
private _isDisposed = false;
|
||||
|
||||
public dispose(): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
if (this._toDispose.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const item of this._toDispose) {
|
||||
item.dispose();
|
||||
}
|
||||
} finally {
|
||||
this._toDispose.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public add<T extends IDisposable>(t: T): T {
|
||||
if (!t) {
|
||||
return t;
|
||||
}
|
||||
if ((t as any) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
|
||||
if (this._isDisposed) {
|
||||
if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
|
||||
console.warn(
|
||||
new Error(
|
||||
'Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!'
|
||||
).stack
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._toDispose.add(t);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue