| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import * as vscode from 'vscode'; | |
| export function activate(_context: vscode.ExtensionContext): void { | |
| vscode.languages.registerDocumentSymbolProvider( | |
| {language: 'upstart'}, | |
| new UpstartSymbolProvider(), | |
| {label: 'ChromiumIDE Upstart'} | |
| ); | |
| } | |
| class UpstartSymbolProvider implements vscode.DocumentSymbolProvider { | |
| provideDocumentSymbols( | |
| document: vscode.TextDocument, | |
| _token: vscode.CancellationToken | |
| ): vscode.DocumentSymbol[] { | |
| const symbols: vscode.DocumentSymbol[] = []; | |
| for (const extractor of extractors) { | |
| let match: RegExpExecArray | null; | |
| while ((match = extractor.regex.exec(document.getText())) !== null) { | |
| const symbol = new vscode.DocumentSymbol( | |
| extractor.name(match), | |
| extractor.detail(match), | |
| extractor.symbolKind, | |
| extractor.range(match, document), | |
| extractor.selectionRange(match, document) | |
| ); | |
| symbols.push(symbol); | |
| } | |
| } | |
| return symbols; | |
| } | |
| } | |
| interface StanzaExtractor { | |
| regex: RegExp; | |
| symbolKind: vscode.SymbolKind; | |
| /** For example, 'script' or variable name. */ | |
| name(match: RegExpExecArray): string; | |
| /** Modifier, for example, 'post-exec'. */ | |
| detail(match: RegExpExecArray): string; | |
| /** Full range described by a symbol, for example, everything in 'script (...) end script' */ | |
| range(match: RegExpExecArray, document: vscode.TextDocument): vscode.Range; | |
| /** Smaller range where a symbol is located. */ | |
| selectionRange( | |
| match: RegExpExecArray, | |
| document: vscode.TextDocument | |
| ): vscode.Range; | |
| } | |
| function groupRange( | |
| match: RegExpExecArray, | |
| document: vscode.TextDocument, | |
| group: number, | |
| offset?: number | |
| ): vscode.Range { | |
| return new vscode.Range( | |
| document.positionAt(match.index + (offset ?? 0)), | |
| document.positionAt(match.index + (offset ?? 0) + match[group].length) | |
| ); | |
| } | |
| const extractors: StanzaExtractor[] = [ | |
| // env | |
| { | |
| // The regex does not allow leading spaces (they would match | |
| // a different 'env' inside script) and multiple spaces between 'env' and | |
| // the variable name (all platform2 conf files have one space). | |
| // This behavior matches how our current syntax highlighting works. | |
| regex: /^env ([A-Za-z0-9_]+)(=.*)?/gm, | |
| symbolKind: vscode.SymbolKind.Variable, | |
| name(match: RegExpExecArray) { | |
| return match[1]; | |
| }, | |
| detail(_match: RegExpExecArray) { | |
| return ''; | |
| }, | |
| range(match: RegExpExecArray, document: vscode.TextDocument) { | |
| // entire line, from env to the assigned value | |
| return groupRange(match, document, 0); | |
| }, | |
| selectionRange(match: RegExpExecArray, document: vscode.TextDocument) { | |
| // just the variable name | |
| return groupRange(match, document, 1, 4); | |
| }, | |
| }, | |
| // script | |
| { | |
| // We need .*? instead of .* to avoid greedy matches, | |
| // which would merge all scripts into one. | |
| regex: | |
| /^((pre-start |post-start |pre-stop |post-stop )?script).*?\nend script/gms, | |
| symbolKind: vscode.SymbolKind.Function, | |
| name(_match: RegExpExecArray) { | |
| return 'script'; | |
| }, | |
| detail(match: RegExpExecArray) { | |
| return match[2] ? match[2].trim() : ''; | |
| }, | |
| range(match: RegExpExecArray, document: vscode.TextDocument) { | |
| return groupRange(match, document, 0); | |
| }, | |
| selectionRange(match: RegExpExecArray, document: vscode.TextDocument) { | |
| return groupRange(match, document, 1); | |
| }, | |
| }, | |
| // exec | |
| { | |
| regex: /^((pre-start |post-start |pre-stop |post-stop )?exec).*?[^\\]$/gms, | |
| symbolKind: vscode.SymbolKind.Function, | |
| name(_match: RegExpExecArray) { | |
| return 'exec'; | |
| }, | |
| detail(match: RegExpExecArray) { | |
| return match[2] ? match[2].trim() : ''; | |
| }, | |
| range(match: RegExpExecArray, document: vscode.TextDocument) { | |
| return groupRange(match, document, 0); | |
| }, | |
| selectionRange(match: RegExpExecArray, document: vscode.TextDocument) { | |
| return groupRange(match, document, 1); | |
| }, | |
| }, | |
| ]; | |
| export const TEST_ONLY = {UpstartSymbolProvider}; |