Implement diagnosis.

This commit is contained in:
CismonX 2020-10-24 23:51:44 +08:00
parent c52c7b5924
commit 2c5aec48ed
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
5 changed files with 92 additions and 18 deletions

53
src/diagnosis.ts Normal file
View File

@ -0,0 +1,53 @@
/**
* diagnosis.ts
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
import { isDefined, lineNumToRange } from './utils';
/**
* Manage diagnostic information of Texinfo documents.
*/
export default class Diagnosis implements vscode.Disposable {
private static singleton?: Diagnosis;
static get instance() {
return Diagnosis.singleton ??= new Diagnosis();
}
private readonly diagnostics = vscode.languages.createDiagnosticCollection('texinfo');
dispose() {
this.diagnostics.dispose();
}
/**
* Generate diagnostic information based on error log from `makeinfo`.
*
* @param document
* @param logText
*/
update(document: vscode.TextDocument, logText: string) {
const fileName = document.uri.path;
const diagnostics = logText.split('\n').filter(line => line.startsWith(fileName))
.map(line => logLineToDiagnostic(line.substring(fileName.length + 1))).filter(isDefined);
this.diagnostics.set(document.uri, diagnostics);
}
delete(document: vscode.TextDocument) {
this.diagnostics.delete(document.uri);
}
}
function logLineToDiagnostic(lineText: string) {
const lineNum = Number.parseInt(lineText) - 1;
// Ignore error that does not correspond a line.
if (Number.isNaN(lineNum)) return undefined;
const message = lineText.substring(lineNum.toString().length + 2);
const severity = message.startsWith('warning:') ? vscode.DiagnosticSeverity.Warning : undefined;
return new vscode.Diagnostic(lineNumToRange(lineNum), message, severity);
}

View File

@ -6,6 +6,7 @@
*/
import * as vscode from 'vscode';
import Diagnosis from './diagnosis';
import Document from './document';
import Logger from './logger';
import Options from './options';
@ -25,6 +26,7 @@ export function activate(context: vscode.ExtensionContext) {
vscode.languages.registerCompletionItemProvider('texinfo', new CompletionItemProvider(), '@'),
vscode.languages.registerFoldingRangeProvider('texinfo', new FoldingRangeProvider()),
vscode.languages.registerDocumentSymbolProvider('texinfo', new DocumentSymbolProvider()),
Diagnosis.instance,
);
}

View File

@ -8,6 +8,7 @@
import * as path from 'path';
import * as vscode from 'vscode';
import Converter from './converter';
import Diagnosis from './diagnosis';
import Document from './document';
import Logger from './logger';
import Options from './options';
@ -70,6 +71,8 @@ export default class Preview {
this.disposables.forEach(event => event.dispose());
this.panel.dispose();
this.documentContext.closePreview();
// Only show diagnostic information when the preview is active.
Diagnosis.instance.delete(this.document);
}
async updateWebview() {
@ -81,20 +84,23 @@ export default class Preview {
this.pendingUpdate = false;
// Inform the user that the preview is updating if `makeinfo` takes too long.
setTimeout(() => this.updating && this.updateTitle(), 500);
let htmlCode = await Converter.convertToHtml(this.document.fileName);
if (htmlCode === undefined) {
const { data, error } = await Converter.convertToHtml(this.document.fileName);
if (error) {
Logger.instance.log(error);
Diagnosis.instance.update(this.document, error);
}
if (data === undefined) {
prompt(`Failed to show preview for ${this.document.fileName}.`, 'Show log', true)
.then(result => result && Logger.show());
.then(result => result && Logger.instance.show());
} else if (Options.displayImage) {
const pathName = path.dirname(this.document.fileName);
// To display images in webviews, image URIs in HTML should be converted to VSCode-recognizable ones.
this.panel.webview.html = transformHtmlImageUri(data, src => {
const srcUri = vscode.Uri.file(pathName + '/' + src);
return this.panel.webview.asWebviewUri(srcUri).toString();
});
} else {
if (Options.displayImage) {
const pathName = path.dirname(this.document.fileName);
// To display images in webviews, image URIs in HTML should be converted to VSCode-recognizable ones.
htmlCode = transformHtmlImageUri(htmlCode, src => {
const srcUri = vscode.Uri.file(pathName + '/' + src);
return this.panel.webview.asWebviewUri(srcUri).toString();
});
}
this.panel.webview.html = htmlCode;
this.panel.webview.html = data;
}
this.updating = false;
this.updateTitle();

View File

@ -8,7 +8,7 @@
import * as vscode from 'vscode';
import Document from './document';
import { FoldingRange } from './folding';
import { Optional } from './utils';
import { lineNumToRange, Optional } from './utils';
/**
* Provide document symbol information for Texinfo documents.
@ -60,11 +60,8 @@ function foldingRangeToSymbols(ranges: readonly RangeNode[], start: number, end:
for (let idx = start; idx < end; ++idx) {
const node = ranges[idx];
if (node === undefined) continue;
const startPosition = new vscode.Position(idx, 0);
const endFirstLine = new vscode.Position(idx, Number.MAX_SAFE_INTEGER);
const endLastLine = new vscode.Position(node.end, Number.MAX_SAFE_INTEGER);
const range = new vscode.Range(startPosition, endLastLine);
const selectionRange = new vscode.Range(startPosition, endFirstLine);
const range = lineNumToRange(idx, node.end);
const selectionRange = lineNumToRange(idx);
const symbol = new vscode.DocumentSymbol('@' + node.name, node.detail,
vscode.SymbolKind.String, range, selectionRange);
symbol.children = foldingRangeToSymbols(ranges, idx + 1, node.end);

View File

@ -60,6 +60,22 @@ export function transformHtmlImageUri(htmlCode: string, transformer: (src: strin
return elements.length === 0 ? htmlCode : dom.outerHTML;
}
/**
* Convert line numbers to VSCode range.
*
* @param startLine
* @param endLine Default to `startLine`.
*/
export function lineNumToRange(startLine: number, endLine = startLine) {
const startPosition = new vscode.Position(startLine, 0);
const endPosition = new vscode.Position(endLine, Number.MAX_SAFE_INTEGER);
return new vscode.Range(startPosition, endPosition);
}
export function isDefined<T>(value: T | undefined): value is T {
return value !== undefined;
}
export type Optional<T> = T | undefined;
export type ExecResult = { data?: string, error: string };