Implement `CodeLensProvider`.
This commit is contained in:
parent
f64b72d22b
commit
e3d5498c75
14
package.json
14
package.json
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue