import { Autowired, Injectable } from '@opensumi/di'; import { Disposable, IDisposable, IMarkerData, IRelatedInformation, MarkerManager, MarkerSeverity, URI, } from '@opensumi/ide-core-common'; import * as monaco from '@opensumi/ide-monaco'; import { ITextmateTokenizer, ITextmateTokenizerService } from '@opensumi/ide-monaco/lib/browser/contrib/tokenizer'; import { Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, ILanguageService, Language, WorkspaceSymbolProvider, } from '../../common'; import { MonacoDiagnosticCollection } from './diagnostic-collection'; export type Mutable = { -readonly [P in keyof T]: T[P] }; function reviveSeverity(severity: MarkerSeverity): DiagnosticSeverity { switch (severity) { case MarkerSeverity.Error: return DiagnosticSeverity.Error; case MarkerSeverity.Warning: return DiagnosticSeverity.Warning; case MarkerSeverity.Info: return DiagnosticSeverity.Information; case MarkerSeverity.Hint: return DiagnosticSeverity.Hint; } } function reviveRange(startLine: number, startColumn: number, endLine: number, endColumn: number): any { // note: language server range is 0-based, marker is 1-based, so need to deduct 1 here return { start: { line: startLine - 1, character: startColumn - 1, }, end: { line: endLine - 1, character: endColumn - 1, }, }; } function reviveRelated(related: IRelatedInformation): DiagnosticRelatedInformation { return { message: related.message, location: { uri: related.resource, range: reviveRange(related.startLineNumber, related.startColumn, related.endLineNumber, related.endColumn), }, }; } export function reviveMarker(marker: IMarkerData): Diagnostic { const monacoMarker: Diagnostic = { code: typeof marker.codeHref !== 'undefined' ? { value: String(marker.code), target: marker.codeHref, } : marker.code, severity: reviveSeverity(marker.severity) as any, range: reviveRange(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), message: marker.message, source: marker.source, relatedInformation: undefined, tags: marker.tags as number[], }; if (marker.relatedInformation) { monacoMarker.relatedInformation = marker.relatedInformation.map(reviveRelated); } return monacoMarker; } @Injectable() export class LanguageService implements ILanguageService { @Autowired() private markerManager: MarkerManager; @Autowired(ITextmateTokenizer) private textmateService: ITextmateTokenizerService; protected readonly markers = new Map(); readonly workspaceSymbolProviders: WorkspaceSymbolProvider[] = []; constructor() { for (const uri of this.markerManager.getResources()) { this.updateMarkers(new URI(uri)); } this.markerManager.onMarkerChanged((uris) => { if (uris) { uris.forEach((uri) => this.updateMarkers(new URI(uri))); } }); } get languages(): Language[] { return [...this.mergeLanguages(this.textmateService.getLanguages()).values()]; } getLanguage(languageId: string): Language | undefined { return this.mergeLanguages( this.textmateService.getLanguages().filter((language) => language.id === languageId), ).get(languageId); } protected mergeLanguages(registered: monaco.languages.ILanguageExtensionPoint[]): Map> { const languages = new Map>(); for (const { id, aliases, extensions, filenames } of registered) { const merged = languages.get(id) || { id, name: '', extensions: new Set(), filenames: new Set(), }; if (!merged.name && aliases && aliases.length) { merged.name = aliases[0]; } if (extensions && extensions.length) { for (const extension of extensions) { merged.extensions.add(extension); } } if (filenames && filenames.length) { for (const filename of filenames) { merged.filenames.add(filename); } } languages.set(id, merged); } for (const [id, language] of languages) { if (!language.name) { language.name = id; } } return languages; } registerWorkspaceSymbolProvider(provider: WorkspaceSymbolProvider): IDisposable { this.workspaceSymbolProviders.push(provider); return Disposable.create(() => { const index = this.workspaceSymbolProviders.indexOf(provider); if (index !== -1) { this.workspaceSymbolProviders.splice(index, 1); } }); } protected updateMarkers(uri: URI): void { const uriString = uri.toString(); const owners = new Map(); for (const marker of this.markerManager.getMarkers({ resource: uri.toString() })) { const diagnostics = owners.get(marker.type) || []; diagnostics.push(reviveMarker(marker)); owners.set(marker.type, diagnostics); } const toClean = new Set(this.markers.keys()); for (const [owner, diagnostics] of owners) { toClean.delete(owner); const collection = this.markers.get(owner) || new MonacoDiagnosticCollection(owner); collection.set(uriString, diagnostics); this.markers.set(owner, collection); } for (const owner of toClean) { const collection = this.markers.get(owner); if (collection) { collection.set(uriString, []); } } } }