Move html sources into /src/

This commit is contained in:
Alex Dima 2021-11-13 19:40:20 +01:00
parent d2a70a52f5
commit a8df4018f1
No known key found for this signature in database
GPG key ID: 39563C1504FDD0C9
23 changed files with 80 additions and 304 deletions

14
src/html/html.worker.ts Normal file
View file

@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as worker from 'monaco-editor-core/esm/vs/editor/editor.worker';
import { HTMLWorker } from './htmlWorker';
self.onmessage = () => {
// ignore the first message
worker.initialize((ctx, createData) => {
return new HTMLWorker(ctx, createData);
});
};

164
src/html/htmlMode.ts Normal file
View file

@ -0,0 +1,164 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkerManager } from './workerManager';
import type { HTMLWorker } from './htmlWorker';
import { LanguageServiceDefaults } from './monaco.contribution';
import * as languageFeatures from './languageFeatures';
import { Uri, IDisposable, languages } from '../fillers/monaco-editor-core';
export function setupMode1(defaults: LanguageServiceDefaults): void {
const client = new WorkerManager(defaults);
const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<HTMLWorker> => {
return client.getLanguageServiceWorker(...uris);
};
let languageId = defaults.languageId;
// all modes
languages.registerCompletionItemProvider(
languageId,
new languageFeatures.CompletionAdapter(worker)
);
languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker));
languages.registerDocumentHighlightProvider(
languageId,
new languageFeatures.DocumentHighlightAdapter(worker)
);
languages.registerLinkProvider(languageId, new languageFeatures.DocumentLinkAdapter(worker));
languages.registerFoldingRangeProvider(
languageId,
new languageFeatures.FoldingRangeAdapter(worker)
);
languages.registerDocumentSymbolProvider(
languageId,
new languageFeatures.DocumentSymbolAdapter(worker)
);
languages.registerSelectionRangeProvider(
languageId,
new languageFeatures.SelectionRangeAdapter(worker)
);
languages.registerRenameProvider(languageId, new languageFeatures.RenameAdapter(worker));
// only html
if (languageId === 'html') {
languages.registerDocumentFormattingEditProvider(
languageId,
new languageFeatures.DocumentFormattingEditProvider(worker)
);
languages.registerDocumentRangeFormattingEditProvider(
languageId,
new languageFeatures.DocumentRangeFormattingEditProvider(worker)
);
}
}
export function setupMode(defaults: LanguageServiceDefaults): IDisposable {
const disposables: IDisposable[] = [];
const providers: IDisposable[] = [];
const client = new WorkerManager(defaults);
disposables.push(client);
const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<HTMLWorker> => {
return client.getLanguageServiceWorker(...uris);
};
function registerProviders(): void {
const { languageId, modeConfiguration } = defaults;
disposeAll(providers);
if (modeConfiguration.completionItems) {
providers.push(
languages.registerCompletionItemProvider(
languageId,
new languageFeatures.CompletionAdapter(worker)
)
);
}
if (modeConfiguration.hovers) {
providers.push(
languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker))
);
}
if (modeConfiguration.documentHighlights) {
providers.push(
languages.registerDocumentHighlightProvider(
languageId,
new languageFeatures.DocumentHighlightAdapter(worker)
)
);
}
if (modeConfiguration.links) {
providers.push(
languages.registerLinkProvider(languageId, new languageFeatures.DocumentLinkAdapter(worker))
);
}
if (modeConfiguration.documentSymbols) {
providers.push(
languages.registerDocumentSymbolProvider(
languageId,
new languageFeatures.DocumentSymbolAdapter(worker)
)
);
}
if (modeConfiguration.rename) {
providers.push(
languages.registerRenameProvider(languageId, new languageFeatures.RenameAdapter(worker))
);
}
if (modeConfiguration.foldingRanges) {
providers.push(
languages.registerFoldingRangeProvider(
languageId,
new languageFeatures.FoldingRangeAdapter(worker)
)
);
}
if (modeConfiguration.selectionRanges) {
providers.push(
languages.registerSelectionRangeProvider(
languageId,
new languageFeatures.SelectionRangeAdapter(worker)
)
);
}
if (modeConfiguration.documentFormattingEdits) {
providers.push(
languages.registerDocumentFormattingEditProvider(
languageId,
new languageFeatures.DocumentFormattingEditProvider(worker)
)
);
}
if (modeConfiguration.documentRangeFormattingEdits) {
providers.push(
languages.registerDocumentRangeFormattingEditProvider(
languageId,
new languageFeatures.DocumentRangeFormattingEditProvider(worker)
)
);
}
}
registerProviders();
disposables.push(asDisposable(providers));
return asDisposable(disposables);
}
function asDisposable(disposables: IDisposable[]): IDisposable {
return { dispose: () => disposeAll(disposables) };
}
function disposeAll(disposables: IDisposable[]) {
while (disposables.length) {
disposables.pop().dispose();
}
}

137
src/html/htmlWorker.ts Normal file
View file

@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { worker } from '../fillers/monaco-editor-core';
import * as htmlService from 'vscode-html-languageservice';
import type { Options } from './monaco.contribution';
import { IHTMLDataProvider } from 'vscode-html-languageservice';
export class HTMLWorker {
private _ctx: worker.IWorkerContext;
private _languageService: htmlService.LanguageService;
private _languageSettings: Options;
private _languageId: string;
constructor(ctx: worker.IWorkerContext, createData: ICreateData) {
this._ctx = ctx;
this._languageSettings = createData.languageSettings;
this._languageId = createData.languageId;
const data = this._languageSettings.data;
const useDefaultDataProvider = data?.useDefaultDataProvider;
const customDataProviders: IHTMLDataProvider[] = [];
if (data?.dataProviders) {
for (const id in data.dataProviders) {
customDataProviders.push(htmlService.newHTMLDataProvider(id, data.dataProviders[id]));
}
}
this._languageService = htmlService.getLanguageService({
useDefaultDataProvider,
customDataProviders
});
}
async doComplete(
uri: string,
position: htmlService.Position
): Promise<htmlService.CompletionList> {
let document = this._getTextDocument(uri);
let htmlDocument = this._languageService.parseHTMLDocument(document);
return Promise.resolve(
this._languageService.doComplete(
document,
position,
htmlDocument,
this._languageSettings && this._languageSettings.suggest
)
);
}
async format(
uri: string,
range: htmlService.Range,
options: htmlService.FormattingOptions
): Promise<htmlService.TextEdit[]> {
let document = this._getTextDocument(uri);
let formattingOptions = { ...this._languageSettings.format, ...options };
let textEdits = this._languageService.format(document, range, formattingOptions);
return Promise.resolve(textEdits);
}
async doHover(uri: string, position: htmlService.Position): Promise<htmlService.Hover> {
let document = this._getTextDocument(uri);
let htmlDocument = this._languageService.parseHTMLDocument(document);
let hover = this._languageService.doHover(document, position, htmlDocument);
return Promise.resolve(hover);
}
async findDocumentHighlights(
uri: string,
position: htmlService.Position
): Promise<htmlService.DocumentHighlight[]> {
let document = this._getTextDocument(uri);
let htmlDocument = this._languageService.parseHTMLDocument(document);
let highlights = this._languageService.findDocumentHighlights(document, position, htmlDocument);
return Promise.resolve(highlights);
}
async findDocumentLinks(uri: string): Promise<htmlService.DocumentLink[]> {
let document = this._getTextDocument(uri);
let links = this._languageService.findDocumentLinks(document, null);
return Promise.resolve(links);
}
async findDocumentSymbols(uri: string): Promise<htmlService.SymbolInformation[]> {
let document = this._getTextDocument(uri);
let htmlDocument = this._languageService.parseHTMLDocument(document);
let symbols = this._languageService.findDocumentSymbols(document, htmlDocument);
return Promise.resolve(symbols);
}
async getFoldingRanges(
uri: string,
context?: { rangeLimit?: number }
): Promise<htmlService.FoldingRange[]> {
let document = this._getTextDocument(uri);
let ranges = this._languageService.getFoldingRanges(document, context);
return Promise.resolve(ranges);
}
async getSelectionRanges(
uri: string,
positions: htmlService.Position[]
): Promise<htmlService.SelectionRange[]> {
let document = this._getTextDocument(uri);
let ranges = this._languageService.getSelectionRanges(document, positions);
return Promise.resolve(ranges);
}
async doRename(
uri: string,
position: htmlService.Position,
newName: string
): Promise<htmlService.WorkspaceEdit> {
let document = this._getTextDocument(uri);
let htmlDocument = this._languageService.parseHTMLDocument(document);
let renames = this._languageService.doRename(document, position, newName, htmlDocument);
return Promise.resolve(renames);
}
private _getTextDocument(uri: string): htmlService.TextDocument {
let models = this._ctx.getMirrorModels();
for (let model of models) {
if (model.uri.toString() === uri) {
return htmlService.TextDocument.create(
uri,
this._languageId,
model.version,
model.getValue()
);
}
}
return null;
}
}
export interface ICreateData {
languageId: string;
languageSettings: Options;
}
export function create(ctx: worker.IWorkerContext, createData: ICreateData): HTMLWorker {
return new HTMLWorker(ctx, createData);
}

View file

@ -0,0 +1,608 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { HTMLWorker } from './htmlWorker';
import * as lsTypes from 'vscode-languageserver-types';
import {
languages,
editor,
Uri,
Position,
Range,
CancellationToken,
IMarkdownString
} from '../fillers/monaco-editor-core';
export interface WorkerAccessor {
(...more: Uri[]): Promise<HTMLWorker>;
}
// --- completion ------
function fromPosition(position: Position): lsTypes.Position {
if (!position) {
return void 0;
}
return { character: position.column - 1, line: position.lineNumber - 1 };
}
function fromRange(range: Range): lsTypes.Range {
if (!range) {
return void 0;
}
return {
start: fromPosition(range.getStartPosition()),
end: fromPosition(range.getEndPosition())
};
}
function toRange(range: lsTypes.Range): Range {
if (!range) {
return void 0;
}
return new Range(
range.start.line + 1,
range.start.character + 1,
range.end.line + 1,
range.end.character + 1
);
}
function isInsertReplaceEdit(
edit: lsTypes.TextEdit | lsTypes.InsertReplaceEdit
): edit is lsTypes.InsertReplaceEdit {
return (
typeof (<lsTypes.InsertReplaceEdit>edit).insert !== 'undefined' &&
typeof (<lsTypes.InsertReplaceEdit>edit).replace !== 'undefined'
);
}
function toCompletionItemKind(kind: number): languages.CompletionItemKind {
const mItemKind = languages.CompletionItemKind;
switch (kind) {
case lsTypes.CompletionItemKind.Text:
return mItemKind.Text;
case lsTypes.CompletionItemKind.Method:
return mItemKind.Method;
case lsTypes.CompletionItemKind.Function:
return mItemKind.Function;
case lsTypes.CompletionItemKind.Constructor:
return mItemKind.Constructor;
case lsTypes.CompletionItemKind.Field:
return mItemKind.Field;
case lsTypes.CompletionItemKind.Variable:
return mItemKind.Variable;
case lsTypes.CompletionItemKind.Class:
return mItemKind.Class;
case lsTypes.CompletionItemKind.Interface:
return mItemKind.Interface;
case lsTypes.CompletionItemKind.Module:
return mItemKind.Module;
case lsTypes.CompletionItemKind.Property:
return mItemKind.Property;
case lsTypes.CompletionItemKind.Unit:
return mItemKind.Unit;
case lsTypes.CompletionItemKind.Value:
return mItemKind.Value;
case lsTypes.CompletionItemKind.Enum:
return mItemKind.Enum;
case lsTypes.CompletionItemKind.Keyword:
return mItemKind.Keyword;
case lsTypes.CompletionItemKind.Snippet:
return mItemKind.Snippet;
case lsTypes.CompletionItemKind.Color:
return mItemKind.Color;
case lsTypes.CompletionItemKind.File:
return mItemKind.File;
case lsTypes.CompletionItemKind.Reference:
return mItemKind.Reference;
}
return mItemKind.Property;
}
function fromCompletionItemKind(kind: languages.CompletionItemKind): lsTypes.CompletionItemKind {
const mItemKind = languages.CompletionItemKind;
switch (kind) {
case mItemKind.Text:
return lsTypes.CompletionItemKind.Text;
case mItemKind.Method:
return lsTypes.CompletionItemKind.Method;
case mItemKind.Function:
return lsTypes.CompletionItemKind.Function;
case mItemKind.Constructor:
return lsTypes.CompletionItemKind.Constructor;
case mItemKind.Field:
return lsTypes.CompletionItemKind.Field;
case mItemKind.Variable:
return lsTypes.CompletionItemKind.Variable;
case mItemKind.Class:
return lsTypes.CompletionItemKind.Class;
case mItemKind.Interface:
return lsTypes.CompletionItemKind.Interface;
case mItemKind.Module:
return lsTypes.CompletionItemKind.Module;
case mItemKind.Property:
return lsTypes.CompletionItemKind.Property;
case mItemKind.Unit:
return lsTypes.CompletionItemKind.Unit;
case mItemKind.Value:
return lsTypes.CompletionItemKind.Value;
case mItemKind.Enum:
return lsTypes.CompletionItemKind.Enum;
case mItemKind.Keyword:
return lsTypes.CompletionItemKind.Keyword;
case mItemKind.Snippet:
return lsTypes.CompletionItemKind.Snippet;
case mItemKind.Color:
return lsTypes.CompletionItemKind.Color;
case mItemKind.File:
return lsTypes.CompletionItemKind.File;
case mItemKind.Reference:
return lsTypes.CompletionItemKind.Reference;
}
return lsTypes.CompletionItemKind.Property;
}
function toTextEdit(textEdit: lsTypes.TextEdit): editor.ISingleEditOperation {
if (!textEdit) {
return void 0;
}
return {
range: toRange(textEdit.range),
text: textEdit.newText
};
}
function toCommand(c: lsTypes.Command | undefined): languages.Command {
return c && c.command === 'editor.action.triggerSuggest'
? { id: c.command, title: c.title, arguments: c.arguments }
: undefined;
}
export class CompletionAdapter implements languages.CompletionItemProvider {
constructor(private _worker: WorkerAccessor) {}
public get triggerCharacters(): string[] {
return ['.', ':', '<', '"', '=', '/'];
}
provideCompletionItems(
model: editor.IReadOnlyModel,
position: Position,
context: languages.CompletionContext,
token: CancellationToken
): Promise<languages.CompletionList> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => {
return worker.doComplete(resource.toString(), fromPosition(position));
})
.then((info) => {
if (!info) {
return;
}
const wordInfo = model.getWordUntilPosition(position);
const wordRange = new Range(
position.lineNumber,
wordInfo.startColumn,
position.lineNumber,
wordInfo.endColumn
);
const items: languages.CompletionItem[] = info.items.map((entry) => {
const item: languages.CompletionItem = {
label: entry.label,
insertText: entry.insertText || entry.label,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
command: toCommand(entry.command),
detail: entry.detail,
range: wordRange,
kind: toCompletionItemKind(entry.kind)
};
if (entry.textEdit) {
if (isInsertReplaceEdit(entry.textEdit)) {
item.range = {
insert: toRange(entry.textEdit.insert),
replace: toRange(entry.textEdit.replace)
};
} else {
item.range = toRange(entry.textEdit.range);
}
item.insertText = entry.textEdit.newText;
}
if (entry.additionalTextEdits) {
item.additionalTextEdits = entry.additionalTextEdits.map(toTextEdit);
}
if (entry.insertTextFormat === lsTypes.InsertTextFormat.Snippet) {
item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
}
return item;
});
return {
isIncomplete: info.isIncomplete,
suggestions: items
};
});
}
}
// --- hover ------
function isMarkupContent(thing: any): thing is lsTypes.MarkupContent {
return (
thing && typeof thing === 'object' && typeof (<lsTypes.MarkupContent>thing).kind === 'string'
);
}
function toMarkdownString(entry: lsTypes.MarkupContent | lsTypes.MarkedString): IMarkdownString {
if (typeof entry === 'string') {
return {
value: entry
};
}
if (isMarkupContent(entry)) {
if (entry.kind === 'plaintext') {
return {
value: entry.value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
};
}
return {
value: entry.value
};
}
return { value: '```' + entry.language + '\n' + entry.value + '\n```\n' };
}
function toMarkedStringArray(
contents: lsTypes.MarkupContent | lsTypes.MarkedString | lsTypes.MarkedString[]
): IMarkdownString[] {
if (!contents) {
return void 0;
}
if (Array.isArray(contents)) {
return contents.map(toMarkdownString);
}
return [toMarkdownString(contents)];
}
export class HoverAdapter implements languages.HoverProvider {
constructor(private _worker: WorkerAccessor) {}
provideHover(
model: editor.IReadOnlyModel,
position: Position,
token: CancellationToken
): Promise<languages.Hover> {
let resource = model.uri;
return this._worker(resource)
.then((worker) => {
return worker.doHover(resource.toString(), fromPosition(position));
})
.then((info) => {
if (!info) {
return;
}
return <languages.Hover>{
range: toRange(info.range),
contents: toMarkedStringArray(info.contents)
};
});
}
}
// --- document highlights ------
function toHighlighKind(kind: lsTypes.DocumentHighlightKind): languages.DocumentHighlightKind {
const mKind = languages.DocumentHighlightKind;
switch (kind) {
case lsTypes.DocumentHighlightKind.Read:
return mKind.Read;
case lsTypes.DocumentHighlightKind.Write:
return mKind.Write;
case lsTypes.DocumentHighlightKind.Text:
return mKind.Text;
}
return mKind.Text;
}
export class DocumentHighlightAdapter implements languages.DocumentHighlightProvider {
constructor(private _worker: WorkerAccessor) {}
public provideDocumentHighlights(
model: editor.IReadOnlyModel,
position: Position,
token: CancellationToken
): Promise<languages.DocumentHighlight[]> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.findDocumentHighlights(resource.toString(), fromPosition(position)))
.then((items) => {
if (!items) {
return;
}
return items.map((item) => ({
range: toRange(item.range),
kind: toHighlighKind(item.kind)
}));
});
}
}
// --- document symbols ------
function toSymbolKind(kind: lsTypes.SymbolKind): languages.SymbolKind {
let mKind = languages.SymbolKind;
switch (kind) {
case lsTypes.SymbolKind.File:
return mKind.Array;
case lsTypes.SymbolKind.Module:
return mKind.Module;
case lsTypes.SymbolKind.Namespace:
return mKind.Namespace;
case lsTypes.SymbolKind.Package:
return mKind.Package;
case lsTypes.SymbolKind.Class:
return mKind.Class;
case lsTypes.SymbolKind.Method:
return mKind.Method;
case lsTypes.SymbolKind.Property:
return mKind.Property;
case lsTypes.SymbolKind.Field:
return mKind.Field;
case lsTypes.SymbolKind.Constructor:
return mKind.Constructor;
case lsTypes.SymbolKind.Enum:
return mKind.Enum;
case lsTypes.SymbolKind.Interface:
return mKind.Interface;
case lsTypes.SymbolKind.Function:
return mKind.Function;
case lsTypes.SymbolKind.Variable:
return mKind.Variable;
case lsTypes.SymbolKind.Constant:
return mKind.Constant;
case lsTypes.SymbolKind.String:
return mKind.String;
case lsTypes.SymbolKind.Number:
return mKind.Number;
case lsTypes.SymbolKind.Boolean:
return mKind.Boolean;
case lsTypes.SymbolKind.Array:
return mKind.Array;
}
return mKind.Function;
}
export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider {
constructor(private _worker: WorkerAccessor) {}
public provideDocumentSymbols(
model: editor.IReadOnlyModel,
token: CancellationToken
): Promise<languages.DocumentSymbol[]> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.findDocumentSymbols(resource.toString()))
.then((items) => {
if (!items) {
return;
}
return items.map((item) => ({
name: item.name,
detail: '',
containerName: item.containerName,
kind: toSymbolKind(item.kind),
tags: [],
range: toRange(item.location.range),
selectionRange: toRange(item.location.range)
}));
});
}
}
export class DocumentLinkAdapter implements languages.LinkProvider {
constructor(private _worker: WorkerAccessor) {}
public provideLinks(
model: editor.IReadOnlyModel,
token: CancellationToken
): Promise<languages.ILinksList> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.findDocumentLinks(resource.toString()))
.then((items) => {
if (!items) {
return;
}
return {
links: items.map((item) => ({
range: toRange(item.range),
url: item.target
}))
};
});
}
}
function fromFormattingOptions(options: languages.FormattingOptions): lsTypes.FormattingOptions {
return {
tabSize: options.tabSize,
insertSpaces: options.insertSpaces
};
}
export class DocumentFormattingEditProvider implements languages.DocumentFormattingEditProvider {
constructor(private _worker: WorkerAccessor) {}
public provideDocumentFormattingEdits(
model: editor.IReadOnlyModel,
options: languages.FormattingOptions,
token: CancellationToken
): Promise<editor.ISingleEditOperation[]> {
const resource = model.uri;
return this._worker(resource).then((worker) => {
return worker
.format(resource.toString(), null, fromFormattingOptions(options))
.then((edits) => {
if (!edits || edits.length === 0) {
return;
}
return edits.map(toTextEdit);
});
});
}
}
export class DocumentRangeFormattingEditProvider
implements languages.DocumentRangeFormattingEditProvider
{
constructor(private _worker: WorkerAccessor) {}
public provideDocumentRangeFormattingEdits(
model: editor.IReadOnlyModel,
range: Range,
options: languages.FormattingOptions,
token: CancellationToken
): Promise<editor.ISingleEditOperation[]> {
const resource = model.uri;
return this._worker(resource).then((worker) => {
return worker
.format(resource.toString(), fromRange(range), fromFormattingOptions(options))
.then((edits) => {
if (!edits || edits.length === 0) {
return;
}
return edits.map(toTextEdit);
});
});
}
}
export class RenameAdapter implements languages.RenameProvider {
constructor(private _worker: WorkerAccessor) {}
provideRenameEdits(
model: editor.IReadOnlyModel,
position: Position,
newName: string,
token: CancellationToken
): Promise<languages.WorkspaceEdit> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => {
return worker.doRename(resource.toString(), fromPosition(position), newName);
})
.then((edit) => {
return toWorkspaceEdit(edit);
});
}
}
function toWorkspaceEdit(edit: lsTypes.WorkspaceEdit): languages.WorkspaceEdit {
if (!edit || !edit.changes) {
return void 0;
}
let resourceEdits: languages.WorkspaceTextEdit[] = [];
for (let uri in edit.changes) {
const _uri = Uri.parse(uri);
for (let e of edit.changes[uri]) {
resourceEdits.push({
resource: _uri,
edit: {
range: toRange(e.range),
text: e.newText
}
});
}
}
return {
edits: resourceEdits
};
}
export class FoldingRangeAdapter implements languages.FoldingRangeProvider {
constructor(private _worker: WorkerAccessor) {}
public provideFoldingRanges(
model: editor.IReadOnlyModel,
context: languages.FoldingContext,
token: CancellationToken
): Promise<languages.FoldingRange[]> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.getFoldingRanges(resource.toString(), context))
.then((ranges) => {
if (!ranges) {
return;
}
return ranges.map((range) => {
const result: languages.FoldingRange = {
start: range.startLine + 1,
end: range.endLine + 1
};
if (typeof range.kind !== 'undefined') {
result.kind = toFoldingRangeKind(<lsTypes.FoldingRangeKind>range.kind);
}
return result;
});
});
}
}
function toFoldingRangeKind(kind: lsTypes.FoldingRangeKind): languages.FoldingRangeKind {
switch (kind) {
case lsTypes.FoldingRangeKind.Comment:
return languages.FoldingRangeKind.Comment;
case lsTypes.FoldingRangeKind.Imports:
return languages.FoldingRangeKind.Imports;
case lsTypes.FoldingRangeKind.Region:
return languages.FoldingRangeKind.Region;
}
}
export class SelectionRangeAdapter implements languages.SelectionRangeProvider {
constructor(private _worker: WorkerAccessor) {}
public provideSelectionRanges(
model: editor.IReadOnlyModel,
positions: Position[],
token: CancellationToken
): Promise<languages.SelectionRange[][]> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.getSelectionRanges(resource.toString(), positions.map(fromPosition)))
.then((selectionRanges) => {
if (!selectionRanges) {
return;
}
return selectionRanges.map((selectionRange) => {
const result: languages.SelectionRange[] = [];
while (selectionRange) {
result.push({ range: toRange(selectionRange.range) });
selectionRange = selectionRange.parent;
}
return result;
});
});
}
}

View file

@ -0,0 +1,329 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as mode from './htmlMode';
import { languages, Emitter, IEvent, IDisposable } from '../fillers/monaco-editor-core';
export interface HTMLFormatConfiguration {
readonly tabSize: number;
readonly insertSpaces: boolean;
readonly wrapLineLength: number;
readonly unformatted: string;
readonly contentUnformatted: string;
readonly indentInnerHtml: boolean;
readonly preserveNewLines: boolean;
readonly maxPreserveNewLines: number;
readonly indentHandlebars: boolean;
readonly endWithNewline: boolean;
readonly extraLiners: string;
readonly wrapAttributes: 'auto' | 'force' | 'force-aligned' | 'force-expand-multiline';
}
export interface CompletionConfiguration {
readonly [providerId: string]: boolean;
}
export interface Options {
/**
* If set, comments are tolerated. If set to false, syntax errors will be emitted for comments.
*/
readonly format?: HTMLFormatConfiguration;
/**
* A list of known schemas and/or associations of schemas to file names.
*/
readonly suggest?: CompletionConfiguration;
/**
* Configures the HTML data types known by the HTML langauge service.
*/
readonly data?: HTMLDataConfiguration;
}
export interface ModeConfiguration {
/**
* Defines whether the built-in completionItemProvider is enabled.
*/
readonly completionItems?: boolean;
/**
* Defines whether the built-in hoverProvider is enabled.
*/
readonly hovers?: boolean;
/**
* Defines whether the built-in documentSymbolProvider is enabled.
*/
readonly documentSymbols?: boolean;
/**
* Defines whether the built-in definitions provider is enabled.
*/
readonly links?: boolean;
/**
* Defines whether the built-in references provider is enabled.
*/
readonly documentHighlights?: boolean;
/**
* Defines whether the built-in rename provider is enabled.
*/
readonly rename?: boolean;
/**
* Defines whether the built-in color provider is enabled.
*/
readonly colors?: boolean;
/**
* Defines whether the built-in foldingRange provider is enabled.
*/
readonly foldingRanges?: boolean;
/**
* Defines whether the built-in diagnostic provider is enabled.
*/
readonly diagnostics?: boolean;
/**
* Defines whether the built-in selection range provider is enabled.
*/
readonly selectionRanges?: boolean;
/**
* Defines whether the built-in documentFormattingEdit provider is enabled.
*/
readonly documentFormattingEdits?: boolean;
/**
* Defines whether the built-in documentRangeFormattingEdit provider is enabled.
*/
readonly documentRangeFormattingEdits?: boolean;
}
export interface LanguageServiceDefaults {
readonly languageId: string;
readonly modeConfiguration: ModeConfiguration;
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly options: Options;
setOptions(options: Options): void;
setModeConfiguration(modeConfiguration: ModeConfiguration): void;
}
// --- HTML configuration and defaults ---------
class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
private _onDidChange = new Emitter<LanguageServiceDefaults>();
private _options: Options;
private _modeConfiguration: ModeConfiguration;
private _languageId: string;
constructor(languageId: string, options: Options, modeConfiguration: ModeConfiguration) {
this._languageId = languageId;
this.setOptions(options);
this.setModeConfiguration(modeConfiguration);
}
get onDidChange(): IEvent<LanguageServiceDefaults> {
return this._onDidChange.event;
}
get languageId(): string {
return this._languageId;
}
get options(): Options {
return this._options;
}
get modeConfiguration(): ModeConfiguration {
return this._modeConfiguration;
}
setOptions(options: Options): void {
this._options = options || Object.create(null);
this._onDidChange.fire(this);
}
setModeConfiguration(modeConfiguration: ModeConfiguration): void {
this._modeConfiguration = modeConfiguration || Object.create(null);
this._onDidChange.fire(this);
}
}
const formatDefaults: Required<HTMLFormatConfiguration> = {
tabSize: 4,
insertSpaces: false,
wrapLineLength: 120,
unformatted:
'default": "a, abbr, acronym, b, bdo, big, br, button, cite, code, dfn, em, i, img, input, kbd, label, map, object, q, samp, select, small, span, strong, sub, sup, textarea, tt, var',
contentUnformatted: 'pre',
indentInnerHtml: false,
preserveNewLines: true,
maxPreserveNewLines: null,
indentHandlebars: false,
endWithNewline: false,
extraLiners: 'head, body, /html',
wrapAttributes: 'auto'
};
const optionsDefault: Required<Options> = {
format: formatDefaults,
suggest: {},
data: { useDefaultDataProvider: true }
};
function getConfigurationDefault(languageId: string): Required<ModeConfiguration> {
return {
completionItems: true,
hovers: true,
documentSymbols: true,
links: true,
documentHighlights: true,
rename: true,
colors: true,
foldingRanges: true,
selectionRanges: true,
diagnostics: languageId === htmlLanguageId, // turned off for Razor and Handlebar
documentFormattingEdits: languageId === htmlLanguageId, // turned off for Razor and Handlebar
documentRangeFormattingEdits: languageId === htmlLanguageId // turned off for Razor and Handlebar
};
}
const htmlLanguageId = 'html';
const handlebarsLanguageId = 'handlebars';
const razorLanguageId = 'razor';
export const htmlLanguageService = registerHTMLLanguageService(
htmlLanguageId,
optionsDefault,
getConfigurationDefault(htmlLanguageId)
);
export const htmlDefaults = htmlLanguageService.defaults;
export const handlebarLanguageService = registerHTMLLanguageService(
handlebarsLanguageId,
optionsDefault,
getConfigurationDefault(handlebarsLanguageId)
);
export const handlebarDefaults = handlebarLanguageService.defaults;
export const razorLanguageService = registerHTMLLanguageService(
razorLanguageId,
optionsDefault,
getConfigurationDefault(razorLanguageId)
);
export const razorDefaults = razorLanguageService.defaults;
// export to the global based API
(<any>languages).html = {
htmlDefaults,
razorDefaults,
handlebarDefaults,
htmlLanguageService,
handlebarLanguageService,
razorLanguageService,
registerHTMLLanguageService
};
// --- Registration to monaco editor ---
declare var AMD: any;
declare var require: any;
function getMode(): Promise<typeof mode> {
if (AMD) {
return new Promise((resolve, reject) => {
require(['vs/language/html/htmlMode'], resolve, reject);
});
} else {
return import('./htmlMode');
}
}
export interface LanguageServiceRegistration extends IDisposable {
readonly defaults: LanguageServiceDefaults;
}
/**
* Registers a new HTML language service for the languageId.
* Note: 'html', 'handlebar' and 'razor' are registered by default.
*
* Use this method to register additional language ids with a HTML service.
* The language server has to be registered before an editor model is opened.
*/
export function registerHTMLLanguageService(
languageId: string,
options: Options = optionsDefault,
modeConfiguration: ModeConfiguration = getConfigurationDefault(languageId)
): LanguageServiceRegistration {
const defaults = new LanguageServiceDefaultsImpl(languageId, options, modeConfiguration);
let mode: IDisposable | undefined;
// delay the initalization of the mode until the language is accessed the first time
const onLanguageListener = languages.onLanguage(languageId, async () => {
mode = (await getMode()).setupMode(defaults);
});
return {
defaults,
dispose() {
onLanguageListener.dispose();
mode?.dispose();
mode = undefined;
}
};
}
export interface HTMLDataConfiguration {
/**
* Defines whether the standard HTML tags and attributes are shown
*/
readonly useDefaultDataProvider?: boolean;
/**
* Provides a set of custom data providers.
*/
readonly dataProviders?: { [providerId: string]: HTMLDataV1 };
}
/**
* Custom HTML tags attributes and attribute values
* https://github.com/microsoft/vscode-html-languageservice/blob/main/docs/customData.md
*/
export interface HTMLDataV1 {
readonly version: 1 | 1.1;
readonly tags?: ITagData[];
readonly globalAttributes?: IAttributeData[];
readonly valueSets?: IValueSet[];
}
export interface IReference {
readonly name: string;
readonly url: string;
}
export interface ITagData {
readonly name: string;
readonly description?: string | MarkupContent;
readonly attributes: IAttributeData[];
readonly references?: IReference[];
}
export interface IAttributeData {
readonly name: string;
readonly description?: string | MarkupContent;
readonly valueSet?: string;
readonly values?: IValueData[];
readonly references?: IReference[];
}
export interface IValueData {
readonly name: string;
readonly description?: string | MarkupContent;
readonly references?: IReference[];
}
export interface IValueSet {
readonly name: string;
readonly values: IValueData[];
}
export interface MarkupContent {
readonly kind: MarkupKind;
readonly value: string;
}
export declare type MarkupKind = 'plaintext' | 'markdown';

89
src/html/workerManager.ts Normal file
View file

@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LanguageServiceDefaults } from './monaco.contribution';
import type { HTMLWorker } from './htmlWorker';
import { Uri, IDisposable, editor } from '../fillers/monaco-editor-core';
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; // 2min
export class WorkerManager {
private _defaults: LanguageServiceDefaults;
private _idleCheckInterval: number;
private _lastUsedTime: number;
private _configChangeListener: IDisposable;
private _worker: editor.MonacoWebWorker<HTMLWorker>;
private _client: Promise<HTMLWorker>;
constructor(defaults: LanguageServiceDefaults) {
this._defaults = defaults;
this._worker = null;
this._idleCheckInterval = window.setInterval(() => this._checkIfIdle(), 30 * 1000);
this._lastUsedTime = 0;
this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker());
}
private _stopWorker(): void {
if (this._worker) {
this._worker.dispose();
this._worker = null;
}
this._client = null;
}
dispose(): void {
clearInterval(this._idleCheckInterval);
this._configChangeListener.dispose();
this._stopWorker();
}
private _checkIfIdle(): void {
if (!this._worker) {
return;
}
let timePassedSinceLastUsed = Date.now() - this._lastUsedTime;
if (timePassedSinceLastUsed > STOP_WHEN_IDLE_FOR) {
this._stopWorker();
}
}
private _getClient(): Promise<HTMLWorker> {
this._lastUsedTime = Date.now();
if (!this._client) {
this._worker = editor.createWebWorker<HTMLWorker>({
// module that exports the create() method and returns a `HTMLWorker` instance
moduleId: 'vs/language/html/htmlWorker',
// passed in to the create() method
createData: {
languageSettings: this._defaults.options,
languageId: this._defaults.languageId
},
label: this._defaults.languageId
});
this._client = <Promise<HTMLWorker>>this._worker.getProxy();
}
return this._client;
}
getLanguageServiceWorker(...resources: Uri[]): Promise<HTMLWorker> {
let _client: HTMLWorker;
return this._getClient()
.then((client) => {
_client = client;
})
.then((_) => {
if (this._worker) {
return this._worker.withSyncedResources(resources);
}
})
.then((_) => _client);
}
}