mirror of
https://github.com/microsoft/monaco-editor.git
synced 2025-12-22 03:30:10 +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
|
|
@ -1,4 +1,2 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
npm run pretty-quick
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default defineConfig({
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
stripInternal: true
|
stripInternal: true
|
||||||
},
|
},
|
||||||
includeExternal: ['monaco-editor-core']
|
includeExternal: ['monaco-editor-core', '@vscode/monaco-lsp-client']
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,15 @@ function getWorkerBootstrapUrl(workerScriptUrl: string | URL) {
|
||||||
import 'vs/nls.messages-loader!';
|
import 'vs/nls.messages-loader!';
|
||||||
export * from '../../../src/editor/editor.main';
|
export * from '../../../src/editor/editor.main';
|
||||||
|
|
||||||
|
// for now, lsp is only available to amd build
|
||||||
|
import * as lsp from '@vscode/monaco-lsp-client';
|
||||||
|
export { lsp };
|
||||||
|
|
||||||
|
// TODO@hediet get rid of the monaco global
|
||||||
|
if ((globalThis as any).monaco) {
|
||||||
|
(globalThis as any).monaco.lsp = lsp;
|
||||||
|
}
|
||||||
|
|
||||||
const styleSheetUrl = require.toUrl('vs/editor/editor.main.css');
|
const styleSheetUrl = require.toUrl('vs/editor/editor.main.css');
|
||||||
|
|
||||||
const link = document.createElement('link');
|
const link = document.createElement('link');
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ export default defineConfig(async (args) => {
|
||||||
/** @type {import('vite').UserConfig} */
|
/** @type {import('vite').UserConfig} */
|
||||||
return {
|
return {
|
||||||
base: './',
|
base: './',
|
||||||
|
resolve: {
|
||||||
|
dedupe: ['monaco-editor-core']
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
lib: {
|
lib: {
|
||||||
cssFileName: 'editor/editor.main',
|
cssFileName: 'editor/editor.main',
|
||||||
|
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
926
package-lock.json
generated
926
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -30,7 +30,8 @@
|
||||||
"watch": "tsc -w -p ./src",
|
"watch": "tsc -w -p ./src",
|
||||||
"build-languages": "ts-node ./build/build-languages",
|
"build-languages": "ts-node ./build/build-languages",
|
||||||
"build-monaco-editor": "ts-node ./build/build-monaco-editor",
|
"build-monaco-editor": "ts-node ./build/build-monaco-editor",
|
||||||
"build": "npm run build-languages && npm run build-monaco-editor"
|
"build-lsp": "cd monaco-lsp-client && npm install && npm run build",
|
||||||
|
"build": "npm run build-lsp && npm run build-languages && npm run build-monaco-editor"
|
||||||
},
|
},
|
||||||
"typings": "./esm/vs/editor/editor.api.d.ts",
|
"typings": "./esm/vs/editor/editor.api.d.ts",
|
||||||
"module": "./esm/vs/editor/editor.main.js",
|
"module": "./esm/vs/editor/editor.main.js",
|
||||||
|
|
@ -39,6 +40,7 @@
|
||||||
"url": "https://github.com/microsoft/monaco-editor"
|
"url": "https://github.com/microsoft/monaco-editor"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vscode/monaco-lsp-client": "file:./monaco-lsp-client",
|
||||||
"@playwright/test": "^1.53.2",
|
"@playwright/test": "^1.53.2",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.2",
|
"@rollup/plugin-node-resolve": "^16.0.2",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue