Implement `CodeLensProvider`.

This commit is contained in:
CismonX 2020-11-11 20:29:30 +08:00
parent f64b72d22b
commit e3d5498c75
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
7 changed files with 143 additions and 13 deletions

View File

@ -82,6 +82,10 @@
"command": "texinfo.preview.show",
"title": "Show preview",
"icon": "$(open-preview)"
},
{
"command": "texinfo.preview.goto",
"title": "Goto node in preview"
}
],
"menus": {
@ -90,6 +94,11 @@
"command": "texinfo.preview.show",
"when": "editorLangId == texinfo",
"group": "navigation"
},
{
"command": "texinfo.preview.goto",
"when": "false",
"group": "navigation"
}
],
"editor/title": [
@ -116,6 +125,11 @@
"default": "makeinfo",
"markdownDescription": "Path to the `makeinfo` (or `texi2any`) command. If not located in `$PATH`, an absolute path should be specified.\n\nThe value should not contain any command-line arguments, just the filename."
},
"texinfo.enableCodeLens": {
"type": "boolean",
"default": true,
"markdownDescription": "Enable code lens on node identifiers which jumps to the corresponding nodes in preview."
},
"texinfo.completion.enableSnippets": {
"type": "boolean",
"default": true,

View File

@ -6,17 +6,21 @@
*/
import * as vscode from 'vscode';
import { lineNumToRange } from '../utils/misc';
import { FoldingRange, Range, NamedLine } from '../utils/types';
/**
* Stores information about folding ranges for a document.
*
* Actually, more than folding ranges (e.g. code lens) is handled within this context, so I believe
* we should use another name...
*/
export default class FoldingRangeContext {
/**
* Regex for matching subsection/section/chapter (-like) commands.
*/
private static nodeMatcher = new RegExp('^@(?:(subsection|unnumberedsubsec|appendixsubsec|subheading)|' +
private static readonly nodeFormat = RegExp('^@(?:(node)|(subsection|unnumberedsubsec|appendixsubsec|subheading)|' +
'(section|unnumberedsec|appendixsec|heading)|(chapter|unnumbered|appendix|majorheading|chapheading)) (.*)$');
/**
@ -26,8 +30,18 @@ export default class FoldingRangeContext {
return this.foldingRanges ?? this.calculateFoldingRanges();
}
/**
* Get node values of document as VSCode code lenses.
*/
get nodeValues() {
this.foldingRanges ?? this.calculateFoldingRanges();
return this.nodes;
}
private foldingRanges?: FoldingRange[];
private nodes = <vscode.CodeLens[]>[];
private commentRange?: Range;
private headerStart?: number;
@ -50,6 +64,7 @@ export default class FoldingRangeContext {
// Clear cached folding range when line count changes.
if (updatedLines !== 1 || event.range.start.line !== event.range.end.line) {
this.foldingRanges = undefined;
this.nodes = [];
return true;
}
}
@ -134,23 +149,32 @@ export default class FoldingRangeContext {
constructor(private readonly document: vscode.TextDocument) {}
private processNode(lineText: string, lineNum: number, lastLineNum: number) {
const result = lineText.match(FoldingRangeContext.nodeMatcher);
const result = lineText.match(FoldingRangeContext.nodeFormat);
if (result === null) return false;
// Subsection level node.
// Node identifier.
if (result[1] !== undefined) {
this.addRange(lineNum, this.closingSubsection ?? lastLineNum, { name: result[1], detail: result[4] });
this.nodes.push(new vscode.CodeLens(lineNumToRange(lineNum), {
title: '$(search-goto-file) Goto node in preview',
command: 'texinfo.preview.goto',
arguments: [this.document, result[5]],
}));
return true;
}
// Subsection level node.
if (result[2] !== undefined) {
this.addRange(lineNum, this.closingSubsection ?? lastLineNum, { name: result[2], detail: result[5] });
this.closingSubsection = this.getLastTextLine(lineNum - 1);
return true;
}
// Section level node.
if (result[2] !== undefined) {
this.addRange(lineNum, this.closingSection ?? lastLineNum, { name: result[2], detail: result[4] });
if (result[3] !== undefined) {
this.addRange(lineNum, this.closingSection ?? lastLineNum, { name: result[3], detail: result[5] });
this.closingSubsection = this.closingSection = this.getLastTextLine(lineNum - 1);
return true;
}
// Chapter level node.
if (result[3] !== undefined) {
this.addRange(lineNum, this.closingChapter ?? lastLineNum, { name: result[3], detail: result[4] });
if (result[4] !== undefined) {
this.addRange(lineNum, this.closingChapter ?? lastLineNum, { name: result[4], detail: result[5] });
this.closingSubsection = this.closingSection = this.closingChapter = this.getLastTextLine(lineNum - 1);
return true;
}
@ -178,6 +202,7 @@ export default class FoldingRangeContext {
private clearTemporaries() {
this.commentRange = undefined;
this.headerStart = undefined;
this.nodes = [];
this.closingSubsection = this.closingSection = this.closingChapter = undefined;
}
}

View File

@ -13,7 +13,7 @@ import Diagnosis from '../diagnosis';
import Logger from '../logger';
import Options from '../options';
import Converter from '../utils/converter';
import { prompt } from '../utils/misc';
import { getNodeHtmlRef, prompt } from '../utils/misc';
import { Operator, Optional } from '../utils/types';
/**
@ -37,6 +37,17 @@ export default class PreviewContext {
ContextMapping.getDocumentContext(document).initPreview().panel.reveal();
}
/**
* Jump to the corresponding section of document preview by node name.
*
* @param document
* @param nodeName
*/
static gotoPreview(document: vscode.TextDocument, nodeName: string) {
ContextMapping.getDocumentContext(document).initPreview().panel.webview
.postMessage({ command: 'goto', value: getNodeHtmlRef(nodeName) });
}
private readonly document = this.documentContext.document;
private readonly panel: vscode.WebviewPanel;
@ -70,7 +81,8 @@ export default class PreviewContext {
this.pendingUpdate = false;
// Inform the user that the preview is updating if `makeinfo` takes too long.
setTimeout(() => this.updating && this.updateTitle(), 500);
const { data, error } = await new Converter(this.document.fileName, this.imageTransformer).convert();
const { data, error } = await new Converter(this.document.fileName, this.imageTransformer, this.script)
.convert();
if (error) {
Logger.log(error);
Diagnosis.update(this.document, error);
@ -97,9 +109,7 @@ export default class PreviewContext {
}
private get imageTransformer(): Optional<Operator<string>> {
if (!Options.displayImage) {
return undefined;
}
if (!Options.displayImage) return undefined;
const pathName = path.dirname(this.document.fileName);
return src => {
const srcUri = vscode.Uri.file(pathName + '/' + src);
@ -108,6 +118,18 @@ export default class PreviewContext {
};
}
private get script() {
if (!Options.enableCodeLens) return undefined;
return "window.addEventListener('message', event => {" +
"const message = event.data;" +
"switch (message.command) {" +
"case 'goto':" +
"window.location.hash = message.value;" +
"break;" +
"}" +
"})";
}
private updateTitle() {
const updating = this.updating ? '(Updating) ' : '';
const fileName = path.basename(this.document.fileName);

View File

@ -11,6 +11,7 @@ import Diagnosis from './diagnosis';
import Logger from './logger';
import Options from './options';
import PreviewContext from './contexts/preview';
import CodeLensProvider from './providers/code_lens';
import CompletionItemProvider from './providers/completion_item';
import DocumentSymbolProvider from './providers/document_symbol';
import FoldingRangeProvider from './providers/folding_range';
@ -23,6 +24,8 @@ export function activate(context: vscode.ExtensionContext) {
vscode.workspace.onDidCloseTextDocument(ContextMapping.onDocumentClose),
vscode.workspace.onDidChangeConfiguration(Options.clear),
vscode.commands.registerTextEditorCommand('texinfo.preview.show', PreviewContext.showPreview),
vscode.commands.registerCommand('texinfo.preview.goto', PreviewContext.gotoPreview),
vscode.languages.registerCodeLensProvider('texinfo', new CodeLensProvider()),
vscode.languages.registerCompletionItemProvider('texinfo', new CompletionItemProvider(), '@'),
vscode.languages.registerDocumentSymbolProvider('texinfo', new DocumentSymbolProvider()),
vscode.languages.registerFoldingRangeProvider('texinfo', new FoldingRangeProvider()),

View File

@ -24,6 +24,10 @@ export default class Options implements vscode.Disposable {
return Options.instance.getString('makeinfo');
}
static get enableCodeLens() {
return Options.instance.getBoolean('enableCodeLens');
}
static get enableSnippets() {
return Options.instance.getBoolean('completion.enableSnippets');
}

View File

@ -0,0 +1,21 @@
/**
* providers/code_lens.ts
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
import ContextMapping from '../context_mapping';
import Options from '../options';
/**
* Provide code lenses for Texinfo document.
*/
export default class CodeLensProvider implements vscode.CodeLensProvider {
provideCodeLenses(document: vscode.TextDocument) {
if (!Options.enableCodeLens) return undefined;
return ContextMapping.getDocumentContext(document).foldingRange.nodeValues;
}
}

View File

@ -53,3 +53,44 @@ export function lineNumToRange(startLine: number, endLine = startLine) {
const endPosition = new vscode.Position(endLine, Number.MAX_SAFE_INTEGER);
return new vscode.Range(startPosition, endPosition);
}
/**
* Check whether character is an alphabet.
*
* @param charCode ASCII code of character.
*/
export function isAlpha(charCode: number) {
return charCode >= 97 && charCode <= 122 || charCode >= 65 && charCode <= 90;
}
/**
* Check whether character is alphanumeric.
*
* @param charCode ASCII code of character.
*/
export function isAlnum(charCode: number) {
return isAlpha(charCode) || charCode >= 48 && charCode <= 57;
}
/**
* Get corresponding HTML cross-reference name by node name.
*
* See section *HTML Cross-reference Node Name Expansion* in the Texinfo manual.
*
* TODO: Node name is not displayed verbatim, leading to wrong HTML xref when containing commands.
* Fix this when migrating to LSP.
*
* @param nodeName
*/
export function getNodeHtmlRef(nodeName: string) {
const result = nodeName.trim().split(/\s+/)
.map(word => word.split('')
.map(ch => {
const charCode = ch.charCodeAt(0);
return isAlnum(charCode) ? ch : '_00' + charCode.toString(16);
})
.join(''))
.join('-');
const firstCharCode = result.charCodeAt(0);
return isAlpha(firstCharCode) ? result : 'g_t_00' + firstCharCode.toString(16) + result.substring(1);
}