diff --git a/src/core/cache.ts b/src/core/cache.ts index 8cd4061a8..8661db72b 100644 --- a/src/core/cache.ts +++ b/src/core/cache.ts @@ -199,7 +199,7 @@ async function refreshCache(filePath: string, rootPath?: string): Promise { updateElements(fileCache) }).finally(() => { - lw.dupLabelDetector.run() + lw.lint.label.check() cachingFilesCount-- promises.delete(filePath) lw.event.fire(lw.event.FileParsed, filePath) diff --git a/src/core/root.ts b/src/core/root.ts index 067e166b5..78e1c8057 100644 --- a/src/core/root.ts +++ b/src/core/root.ts @@ -67,7 +67,7 @@ async function find(): Promise { // We also clean the completions from the old project lw.completer.input.reset() - lw.dupLabelDetector.reset() + lw.lint.label.reset() lw.cache.reset() lw.cache.add(rootFilePath) void lw.cache.refreshCache(rootFilePath).then(async () => { diff --git a/src/lint/bibtex-formatter.ts b/src/lint/bibtex-formatter.ts index 754ff6514..8772c33c3 100644 --- a/src/lint/bibtex-formatter.ts +++ b/src/lint/bibtex-formatter.ts @@ -2,16 +2,22 @@ import * as vscode from 'vscode' import { bibtexParser } from 'latex-utensils' import { performance } from 'perf_hooks' import { lw } from '../lw' -import * as BibtexUtils from './bibtexformatterlib/bibtexutils' +import { bibtexFormat, bibtexSort, getBibtexFormatConfig } from './bibtex-formatter/utils' +import type { BibtexEntry } from './bibtex-formatter/utils' import { parser } from '../parse/parser' const logger = lw.log('Format', 'Bib') +export { + format, + formattingProvider as formatter +} + const duplicatesDiagnostics: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection('BibTeX') const diags: vscode.Diagnostic[] = [] -export async function bibtexFormat(sort: boolean, align: boolean) { +async function format(sort: boolean, align: boolean) { if (!vscode.window.activeTextEditor) { logger.log('Exit formatting. The active textEditor is undefined.') return @@ -47,7 +53,7 @@ export async function bibtexFormat(sort: boolean, align: boolean) { async function formatDocument(document: vscode.TextDocument, sort: boolean, align: boolean, range?: vscode.Range): Promise { // Get configuration - const formatConfig = BibtexUtils.getBibtexFormatConfig(document.uri) + const formatConfig = getBibtexFormatConfig(document.uri) const config = vscode.workspace.getConfiguration('latex-workshop', document) const handleDuplicates = config.get('bibtex-format.handleDuplicates') as 'Ignore Duplicates' | 'Highlight Duplicates' | 'Comment Duplicates' const lineOffset = range ? range.start.line : 0 @@ -60,7 +66,7 @@ async function formatDocument(document: vscode.TextDocument, sort: boolean, alig } logger.log(`Parsed ${ast.content.length} AST items.`) // Create an array of entries and of their starting locations - const entries: BibtexUtils.BibtexEntry[] = [] + const entries: BibtexEntry[] = [] const entryLocations: vscode.Range[] = [] ast.content.forEach(item => { if (bibtexParser.isEntry(item) || bibtexParser.isStringEntry(item)) { @@ -78,7 +84,7 @@ async function formatDocument(document: vscode.TextDocument, sort: boolean, alig let sortedEntryLocations: vscode.Range[] = [] const duplicates = new Set() if (sort) { - entries.sort(BibtexUtils.bibtexSort(duplicates, formatConfig)).forEach(entry => { + entries.sort(bibtexSort(duplicates, formatConfig)).forEach(entry => { sortedEntryLocations.push((new vscode.Range( entry.location.start.line - 1, entry.location.start.column - 1, @@ -99,7 +105,7 @@ async function formatDocument(document: vscode.TextDocument, sort: boolean, alig for (let i = 0; i < entries.length; i++) { if (align && bibtexParser.isEntry(entries[i])) { const entry: bibtexParser.Entry = entries[i] as bibtexParser.Entry - text = BibtexUtils.bibtexFormat(entry, formatConfig) + text = bibtexFormat(entry, formatConfig) } else { text = document.getText(sortedEntryLocations[i]) } @@ -139,7 +145,7 @@ async function formatDocument(document: vscode.TextDocument, sort: boolean, alig return edits } -class BibtexFormatterProvider implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider { +class FormattingProvider implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider { public provideDocumentFormattingEdits(document: vscode.TextDocument, _options: vscode.FormattingOptions, _token: vscode.CancellationToken): vscode.ProviderResult { const sort = vscode.workspace.getConfiguration('latex-workshop', document).get('bibtex-format.sort.enabled') as boolean logger.log('Start bibtex formatting on behalf of VSCode\'s formatter.') @@ -153,4 +159,4 @@ class BibtexFormatterProvider implements vscode.DocumentFormattingEditProvider, } } -export const bibtexFormatterProvider = new BibtexFormatterProvider() +const formattingProvider = new FormattingProvider() diff --git a/src/lint/bibtexformatterlib/bibtexutils.ts b/src/lint/bibtex-formatter/utils.ts similarity index 99% rename from src/lint/bibtexformatterlib/bibtexutils.ts rename to src/lint/bibtex-formatter/utils.ts index 285efa2bb..f85baf9c0 100644 --- a/src/lint/bibtexformatterlib/bibtexutils.ts +++ b/src/lint/bibtex-formatter/utils.ts @@ -27,7 +27,7 @@ function getBibtexFormatTab(tab: string): string | undefined { } } -export type BibtexFormatConfig = { +type BibtexFormatConfig = { tab: string, left: string, right: string, diff --git a/src/lint/duplicate-label.ts b/src/lint/duplicate-label.ts index 13cff0cdb..449ddc746 100644 --- a/src/lint/duplicate-label.ts +++ b/src/lint/duplicate-label.ts @@ -5,7 +5,7 @@ import { lw } from '../lw' const duplicatedLabelsDiagnostics = vscode.languages.createDiagnosticCollection('Duplicate Labels') export const dupLabelDetector = { - run, + check, reset } @@ -40,7 +40,7 @@ function computeDuplicates(): string[] { return duplicates } -function run() { +function check() { const configuration = vscode.workspace.getConfiguration('latex-workshop') if (!configuration.get('check.duplicatedLabels.enabled')) { return diff --git a/src/lint/index.ts b/src/lint/index.ts new file mode 100644 index 000000000..4c0fd94ee --- /dev/null +++ b/src/lint/index.ts @@ -0,0 +1,19 @@ +import { lint as latexLinter } from './latex-linter' +import { formatter as latexFormatter } from './latex-formatter' +import { provider as latexActionProvider, action as latexAction } from './latex-code-actions' +import { format as bibtexFormat, formatter as bibtexFormatter } from './bibtex-formatter' +import { dupLabelDetector } from './duplicate-label' + +export const lint = { + latex: { + formatter: latexFormatter, + actionprovider: latexActionProvider, + action: latexAction, + ...latexLinter + }, + bibtex: { + format: bibtexFormat, + formatter: bibtexFormatter + }, + label: dupLabelDetector +} diff --git a/src/lint/latex-code-actions.ts b/src/lint/latex-code-actions.ts index 7709d5485..7b9850431 100644 --- a/src/lint/latex-code-actions.ts +++ b/src/lint/latex-code-actions.ts @@ -1,6 +1,11 @@ -import * as vs from 'vscode' +import * as vscode from 'vscode' import { TeXMathEnvFinder } from '../preview/math/mathpreviewlib/texmathenvfinder' +export { + provider, + action +} + /** * Each number corresponds to the warning number of ChkTeX. */ @@ -27,19 +32,18 @@ const CODE_TO_ACTION_STRING: {[key: number]: string} = { 46: 'Use \\( ... \\) instead of $ ... $' } -function characterBeforeRange(document: vs.TextDocument, range: vs.Range) { +function characterBeforeRange(document: vscode.TextDocument, range: vscode.Range) { return document.getText(range.with(range.start.translate(0, -1)))[0] } -function isOpeningQuote(document: vs.TextDocument, range: vs.Range) { +function isOpeningQuote(document: vscode.TextDocument, range: vscode.Range) { return range.start.character === 0 || characterBeforeRange(document, range) === ' ' } - -export class CodeActions implements vs.CodeActionProvider { +class CodeActionProvider implements vscode.CodeActionProvider { // Leading underscore to avoid tslint complaint - provideCodeActions(document: vs.TextDocument, _range: vs.Range, context: vs.CodeActionContext, _token: vs.CancellationToken): vs.Command[] { - const actions: vs.Command[] = [] + provideCodeActions(document: vscode.TextDocument, _range: vscode.Range, context: vscode.CodeActionContext, _token: vscode.CancellationToken): vscode.Command[] { + const actions: vscode.Command[] = [] context.diagnostics.filter(d => d.source === 'ChkTeX').forEach(d => { let code = typeof d.code === 'object' ? d.code.value : d.code if (!code) { @@ -60,134 +64,135 @@ export class CodeActions implements vs.CodeActionProvider { return actions } +} - runCodeAction(document: vs.TextDocument, range: vs.Range, code: number, message: string) { - let fixString: string | undefined - let regexResult: RegExpExecArray | null - switch (code) { - case 24: - case 26: - case 39: - case 42: - // In all these cases remove all proceeding whitespace. - void this.replaceWhitespaceOnLineBefore(document, range.end, '') - break - case 4: - case 5: - case 28: - // In all these cases just clear what ChkTeX highlighted. - void this.replaceRangeWithString(document, range, '') - break - case 1: - void this.replaceWhitespaceOnLineBefore(document, range.end.translate(0, -1), '{}') - break - case 2: - void this.replaceWhitespaceOnLineBefore(document, range.end, '~') - break - case 6: - void this.replaceWhitespaceOnLineBefore(document, range.end.translate(0, -1), '\\/') - break - case 11: - // add a space after so we don't accidentally join with the following word. - regexResult = /\\[cl]?dots/.exec(message) - if (!regexResult) { - break - } - fixString = regexResult[0] + ' ' - void this.replaceRangeWithString(document, range, fixString) - break - case 12: - void this.replaceRangeWithString(document, range, '\\ ') - break - case 13: - void this.replaceWhitespaceOnLineBefore(document, range.end.translate(0, -1), '\\@') - break - case 18: - if (isOpeningQuote(document, range)) { - void this.replaceRangeWithRepeatedString(document, range, '``') - } else { - void this.replaceRangeWithRepeatedString(document, range, "''") - } - break - case 32: - void this.replaceRangeWithRepeatedString(document, range, '`') - break - case 33: - void this.replaceRangeWithRepeatedString(document, range, "'") - break - case 34: - if (isOpeningQuote(document, range)) { - void this.replaceRangeWithRepeatedString(document, range, '`') - } else { - void this.replaceRangeWithRepeatedString(document, range, "'") - } - break - case 35: - regexResult = /`(.+)'/.exec(message) - if (!regexResult) { - break - } - fixString = regexResult[1] - void this.replaceRangeWithString(document, range, fixString) - break - case 45: - void this.replaceMathDelimitersInRange(document, range, '$$', '\\[', '\\]') - break - case 46: - void this.replaceMathDelimitersInRange(document, range, '$', '\\(', '\\)') +const provider = new CodeActionProvider() + +function action(document: vscode.TextDocument, range: vscode.Range, code: number, message: string) { + let fixString: string | undefined + let regexResult: RegExpExecArray | null + switch (code) { + case 24: + case 26: + case 39: + case 42: + // In all these cases remove all proceeding whitespace. + void replaceWhitespaceOnLineBefore(document, range.end, '') + break + case 4: + case 5: + case 28: + // In all these cases just clear what ChkTeX highlighted. + void replaceRangeWithString(document, range, '') + break + case 1: + void replaceWhitespaceOnLineBefore(document, range.end.translate(0, -1), '{}') + break + case 2: + void replaceWhitespaceOnLineBefore(document, range.end, '~') + break + case 6: + void replaceWhitespaceOnLineBefore(document, range.end.translate(0, -1), '\\/') + break + case 11: + // add a space after so we don't accidentally join with the following word. + regexResult = /\\[cl]?dots/.exec(message) + if (!regexResult) { break - default: + } + fixString = regexResult[0] + ' ' + void replaceRangeWithString(document, range, fixString) + break + case 12: + void replaceRangeWithString(document, range, '\\ ') + break + case 13: + void replaceWhitespaceOnLineBefore(document, range.end.translate(0, -1), '\\@') + break + case 18: + if (isOpeningQuote(document, range)) { + void replaceRangeWithRepeatedString(document, range, '``') + } else { + void replaceRangeWithRepeatedString(document, range, "''") + } + break + case 32: + void replaceRangeWithRepeatedString(document, range, '`') + break + case 33: + void replaceRangeWithRepeatedString(document, range, "'") + break + case 34: + if (isOpeningQuote(document, range)) { + void replaceRangeWithRepeatedString(document, range, '`') + } else { + void replaceRangeWithRepeatedString(document, range, "'") + } + break + case 35: + regexResult = /`(.+)'/.exec(message) + if (!regexResult) { break - } + } + fixString = regexResult[1] + void replaceRangeWithString(document, range, fixString) + break + case 45: + void replaceMathDelimitersInRange(document, range, '$$', '\\[', '\\]') + break + case 46: + void replaceMathDelimitersInRange(document, range, '$', '\\(', '\\)') + break + default: + break } +} - private replaceWhitespaceOnLineBefore(document: vs.TextDocument, position: vs.Position, replaceWith: string) { - const beforePosRange = new vs.Range(new vs.Position(position.line, 0), position) - const text = document.getText(beforePosRange) - const regexResult = /\s*$/.exec(text) - if (!regexResult) { - return vs.workspace.applyEdit(new vs.WorkspaceEdit()) - } - const charactersToRemove = regexResult[0].length - const wsRange = new vs.Range(new vs.Position(position.line, position.character - charactersToRemove), position) - const edit = new vs.WorkspaceEdit() - edit.replace(document.uri, wsRange, replaceWith) - return vs.workspace.applyEdit(edit) +function replaceWhitespaceOnLineBefore(document: vscode.TextDocument, position: vscode.Position, replaceWith: string) { + const beforePosRange = new vscode.Range(new vscode.Position(position.line, 0), position) + const text = document.getText(beforePosRange) + const regexResult = /\s*$/.exec(text) + if (!regexResult) { + return vscode.workspace.applyEdit(new vscode.WorkspaceEdit()) } + const charactersToRemove = regexResult[0].length + const wsRange = new vscode.Range(new vscode.Position(position.line, position.character - charactersToRemove), position) + const edit = new vscode.WorkspaceEdit() + edit.replace(document.uri, wsRange, replaceWith) + return vscode.workspace.applyEdit(edit) +} - private replaceRangeWithString(document: vs.TextDocument, range: vs.Range, replacementString: string) { - const edit = new vs.WorkspaceEdit() - edit.replace(document.uri, range, replacementString) - return vs.workspace.applyEdit(edit) - } +function replaceRangeWithString(document: vscode.TextDocument, range: vscode.Range, replacementString: string) { + const edit = new vscode.WorkspaceEdit() + edit.replace(document.uri, range, replacementString) + return vscode.workspace.applyEdit(edit) +} - private replaceRangeWithRepeatedString(document: vs.TextDocument, range: vs.Range, replacementString: string) { - return this.replaceRangeWithString(document, range, replacementString.repeat(range.end.character - range.start.character)) - } +function replaceRangeWithRepeatedString(document: vscode.TextDocument, range: vscode.Range, replacementString: string) { + return replaceRangeWithString(document, range, replacementString.repeat(range.end.character - range.start.character)) +} - private replaceMathDelimitersInRange(document: vs.TextDocument, range: vs.Range, oldDelim: '$' | '$$', startDelim: string, endDelim: string) { - const oldDelimLength = oldDelim.length - let endRange = range.with(range.end.translate(0, - oldDelimLength), range.end) - const text = document.getText(endRange) - // Check if the end position really contains the end delimiter. - // This is not the case when the opening and closing delimiters are on different lines - if (text !== oldDelim) { - if (oldDelim === '$$') { - const pat = /(? doc.validateRange(new vscode.Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE)) type OperatingSystem = { @@ -182,7 +185,7 @@ function format(document: vscode.TextDocument, range?: vscode.Range): Thenable, - lintFile(document: vscode.TextDocument): Promise, - parseLog(log: string, filePath?: string): void +export const lint = { + on, + root } -export class Linter { - private linterTimeout?: NodeJS.Timer +let linterTimeout: NodeJS.Timeout | undefined - private getLinters(scope?: vscode.ConfigurationScope): ILinter[] { - const configuration = vscode.workspace.getConfiguration('latex-workshop', scope) - const linters: ILinter[] = [] - if (configuration.get('linting.chktex.enabled')) { - linters.push(chkTeX) - } else { - chkTeX.linterDiagnostics.clear() - } - if (configuration.get('linting.lacheck.enabled')) { - linters.push(laCheck) - } else { - laCheck.linterDiagnostics.clear() - } - return linters +function getLinters(scope?: vscode.ConfigurationScope): LaTeXLinter[] { + const configuration = vscode.workspace.getConfiguration('latex-workshop', scope) + const linters: LaTeXLinter[] = [] + if (configuration.get('linting.chktex.enabled')) { + linters.push(chkTeX) + } else { + chkTeX.linterDiagnostics.clear() } - - lintRootFileIfEnabled() { - const linters = this.getLinters(lw.root.getWorkspace()) - linters.forEach(linter => { - if (lw.root.file.path === undefined) { - logger.log(`No root file found for ${linter.getName()}.`) - return - } - logger.log(`${linter.getName()} lints root ${lw.root.file.path} .`) - void linter.lintRootFile(lw.root.file.path) - }) + if (configuration.get('linting.lacheck.enabled')) { + linters.push(laCheck) + } else { + laCheck.linterDiagnostics.clear() } + return linters +} + +function root() { + const linters = getLinters(lw.root.getWorkspace()) + linters.forEach(linter => { + if (lw.root.file.path === undefined) { + logger.log(`No root file found for ${linter.getName()}.`) + return + } + logger.log(`${linter.getName()} lints root ${lw.root.file.path} .`) + void linter.lintRootFile(lw.root.file.path) + }) +} - lintActiveFileIfEnabledAfterInterval(document: vscode.TextDocument) { - const configuration = vscode.workspace.getConfiguration('latex-workshop', document.uri) - const linters = this.getLinters(document.uri) - if (linters.length > 0 - && (configuration.get('linting.run') as string) === 'onType') { - const interval = configuration.get('linting.delay') as number - if (this.linterTimeout) { - clearTimeout(this.linterTimeout) - } - this.linterTimeout = setTimeout(() => linters.forEach(linter => { - logger.log(`${linter.getName()} lints ${document.fileName} .`) - void linter.lintFile(document) - }), interval) +function on(document: vscode.TextDocument) { + const configuration = vscode.workspace.getConfiguration('latex-workshop', document.uri) + const linters = getLinters(document.uri) + if (linters.length > 0 + && (configuration.get('linting.run') as string) === 'onType') { + const interval = configuration.get('linting.delay') as number + if (linterTimeout) { + clearTimeout(linterTimeout) } + linterTimeout = setTimeout(() => linters.forEach(linter => { + logger.log(`${linter.getName()} lints ${document.fileName} .`) + void linter.lintFile(document) + }), interval) } } diff --git a/src/lint/linterlib/chktex.ts b/src/lint/latex-linter/chktex.ts similarity index 95% rename from src/lint/linterlib/chktex.ts rename to src/lint/latex-linter/chktex.ts index a385d1910..81e50c3dc 100644 --- a/src/lint/linterlib/chktex.ts +++ b/src/lint/latex-linter/chktex.ts @@ -4,15 +4,21 @@ import * as fs from 'fs' import * as os from 'os' import { ChildProcessWithoutNullStreams, spawn } from 'child_process' import { lw } from '../../lw' -import type { ILinter } from '../latex-linter' -import { processWrapper } from './linterutils' +import type { LaTeXLinter } from '../../types' +import { processWrapper } from './utils' import { convertFilenameEncoding } from '../../utils/convertfilename' - const logger = lw.log('Linter', 'ChkTeX') +export const chkTeX: LaTeXLinter = { + linterDiagnostics: vscode.languages.createDiagnosticCollection(getName()), + getName, + lintFile, + lintRootFile, + parseLog +} + const linterName = 'ChkTeX' -const linterDiagnostics: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection(linterName) let linterProcess: ChildProcessWithoutNullStreams | undefined function getName() { @@ -191,11 +197,11 @@ function parseLog(log: string, singleFileOriginalPath?: string, tabSizeArg?: num logger.log(`Logged ${linterLog.length} messages.`) if (singleFileOriginalPath === undefined) { // A full lint of the project has taken place - clear all previous results. - linterDiagnostics.clear() + chkTeX.linterDiagnostics.clear() } else if (linterLog.length === 0) { // We are linting a single file and the new log is empty for it - // clean existing records. - linterDiagnostics.set(vscode.Uri.file(singleFileOriginalPath), []) + chkTeX.linterDiagnostics.set(vscode.Uri.file(singleFileOriginalPath), []) } showLinterDiagnostics(linterLog) } @@ -277,7 +283,7 @@ function showLinterDiagnostics(linterLog: ChkTeXLogEntry[]) { file1 = f } } - linterDiagnostics.set(vscode.Uri.file(file1), diagsCollection[file]) + chkTeX.linterDiagnostics.set(vscode.Uri.file(file1), diagsCollection[file]) } } } @@ -297,11 +303,3 @@ const DIAGNOSTIC_SEVERITY: { [key: string]: vscode.DiagnosticSeverity } = { 'warning': vscode.DiagnosticSeverity.Warning, 'error': vscode.DiagnosticSeverity.Error, } - -export const chkTeX: ILinter = { - linterDiagnostics, - getName, - lintFile, - lintRootFile, - parseLog -} diff --git a/src/lint/linterlib/lacheck.ts b/src/lint/latex-linter/lacheck.ts similarity index 92% rename from src/lint/linterlib/lacheck.ts rename to src/lint/latex-linter/lacheck.ts index c70dda7cd..d35266590 100644 --- a/src/lint/linterlib/lacheck.ts +++ b/src/lint/latex-linter/lacheck.ts @@ -3,15 +3,21 @@ import * as path from 'path' import * as fs from 'fs' import { ChildProcessWithoutNullStreams, spawn } from 'child_process' import { lw } from '../../lw' -import type { ILinter } from '../latex-linter' -import { processWrapper } from './linterutils' +import type { LaTeXLinter } from '../../types' +import { processWrapper } from './utils' import { convertFilenameEncoding } from '../../utils/convertfilename' - const logger = lw.log('Linter', 'LaCheck') +export const laCheck: LaTeXLinter = { + linterDiagnostics: vscode.languages.createDiagnosticCollection(getName()), + getName, + lintFile, + lintRootFile, + parseLog +} + const linterName = 'LaCheck' -const linterDiagnostics: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection(linterName) let linterProcess: ChildProcessWithoutNullStreams | undefined function getName() { @@ -97,7 +103,7 @@ function parseLog(log: string, filePath?: string) { } } logger.log(`Logged ${linterLog.length} messages.`) - linterDiagnostics.clear() + laCheck.linterDiagnostics.clear() showLinterDiagnostics(linterLog) } @@ -128,19 +134,11 @@ function showLinterDiagnostics(linterLog: LaCheckLogEntry[]) { file1 = f } } - linterDiagnostics.set(vscode.Uri.file(file1), diagsCollection[file]) + laCheck.linterDiagnostics.set(vscode.Uri.file(file1), diagsCollection[file]) } } } -export const laCheck: ILinter = { - linterDiagnostics, - getName, - lintFile, - lintRootFile, - parseLog -} - interface LaCheckLogEntry { file: string, line: number, diff --git a/src/lint/linterlib/linterutils.ts b/src/lint/latex-linter/utils.ts similarity index 100% rename from src/lint/linterlib/linterutils.ts rename to src/lint/latex-linter/utils.ts diff --git a/src/lw.ts b/src/lw.ts index 6807a7404..924b41305 100644 --- a/src/lw.ts +++ b/src/lw.ts @@ -8,17 +8,15 @@ import type { root } from './core/root' import type { compile } from './compile' import type { server, viewer } from './preview' import type { locate } from './locate' +import type { lint } from './lint' import type { Cleaner } from './extras/cleaner' import type { LaTeXCommanderTreeView } from './extras/activity-bar' import type { Counter } from './extras/counter' -import type { Linter } from './lint/latex-linter' import type { MathPreviewPanel } from './extras/math-preview-panel' import type { Section } from './extras/section' -import type { dupLabelDetector } from './lint/duplicate-label' import type { SnippetView } from './extras/snippet-view' import type { TeXMagician } from './extras/texroot' -import type { CodeActions } from './lint/latex-code-actions' import type { AtSuggestionCompleter, Completer } from './completion/latex' import type { GraphicsPreview } from './preview/graphics' import type { MathPreview } from './preview/math/mathpreview' @@ -42,14 +40,12 @@ export const lw = { locate: {} as typeof locate, completer: Object.create(null) as Completer, atSuggestionCompleter: Object.create(null) as AtSuggestionCompleter, - linter: Object.create(null) as Linter, + lint: {} as typeof lint, cleaner: Object.create(null) as Cleaner, counter: Object.create(null) as Counter, texdoc: Object.create(null) as TeXDoc, - codeActions: Object.create(null) as CodeActions, texMagician: Object.create(null) as TeXMagician, section: Object.create(null) as Section, - dupLabelDetector: Object.create(null) as typeof dupLabelDetector, latexCommanderTreeView: Object.create(null) as LaTeXCommanderTreeView, structureViewer: Object.create(null) as StructureView, snippetView: Object.create(null) as SnippetView, diff --git a/src/main.ts b/src/main.ts index cb943f512..bd9910fe2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,8 @@ lw.server = server lw.viewer = viewer import { locate } from './locate' lw.locate = locate +import { lint } from './lint' +lw.lint = lint import { MathPreviewPanelSerializer } from './extras/math-preview-panel' import { BibtexCompleter } from './completion/bibtex' @@ -28,21 +30,16 @@ import { HoverProvider } from './preview/hover' import { DocSymbolProvider } from './language/symbol-document' import { ProjectSymbolProvider } from './language/symbol-project' import { DefinitionProvider } from './language/definition' -import { latexFormatterProvider } from './lint/latex-formatter' import { FoldingProvider, WeaveFoldingProvider } from './language/folding' import { SelectionRangeProvider } from './language/selection' -import { bibtexFormat, bibtexFormatterProvider } from './lint/bibtex-formatter' import { Cleaner } from './extras/cleaner' import { LaTeXCommanderTreeView } from './extras/activity-bar' import { Counter } from './extras/counter' -import { dupLabelDetector } from './lint/duplicate-label' -import { Linter } from './lint/latex-linter' import { MathPreviewPanel } from './extras/math-preview-panel' import { Section } from './extras/section' import { SnippetView } from './extras/snippet-view' import { TeXMagician } from './extras/texroot' -import { CodeActions } from './lint/latex-code-actions' import { AtSuggestionCompleter, Completer } from './completion/latex' import { GraphicsPreview } from './preview/graphics' import { MathPreview } from './preview/math/mathpreview' @@ -58,14 +55,11 @@ function initialize(extensionContext: vscode.ExtensionContext) { lw.onDispose(undefined, extensionContext.subscriptions) lw.completer = new Completer() lw.atSuggestionCompleter = new AtSuggestionCompleter() - lw.linter = new Linter() lw.cleaner = new Cleaner() lw.counter = new Counter() lw.texdoc = new TeXDoc() - lw.codeActions = new CodeActions() lw.texMagician = new TeXMagician() lw.section = new Section() - lw.dupLabelDetector = dupLabelDetector lw.latexCommanderTreeView = new LaTeXCommanderTreeView() lw.structureViewer = new StructureView() lw.snippetView = new SnippetView() @@ -118,7 +112,7 @@ export function activate(extensionContext: vscode.ExtensionContext) { lw.cache.getIncludedTeX(lw.root.file.path, [], false).includes(e.fileName) || lw.cache.getIncludedBib().includes(e.fileName)) { logger.log(`onDidSaveTextDocument triggered: ${e.uri.toString(true)}`) - lw.linter.lintRootFileIfEnabled() + lw.lint.latex.root() void lw.compile.autoBuild(e.fileName, 'onSave') lw.counter.countOnSaveIfEnabled(e.fileName) } @@ -145,7 +139,7 @@ export function activate(extensionContext: vscode.ExtensionContext) { if (e && lw.file.hasTexLangId(e.document.languageId) && e.document.fileName !== prevTeXDocumentPath) { prevTeXDocumentPath = e.document.fileName await lw.root.find() - lw.linter.lintRootFileIfEnabled() + lw.lint.latex.root() } else if (!e || !lw.file.hasBibLangId(e.document.languageId)) { isLaTeXActive = false } @@ -161,7 +155,7 @@ export function activate(extensionContext: vscode.ExtensionContext) { return } lw.event.fire(lw.event.DocumentChanged) - lw.linter.lintActiveFileIfEnabledAfterInterval(e.document) + lw.lint.latex.on(e.document) lw.cache.refreshCacheAggressive(e.document.fileName) })) @@ -177,7 +171,7 @@ export function activate(extensionContext: vscode.ExtensionContext) { registerProviders(extensionContext) void lw.root.find().then(() => { - lw.linter.lintRootFileIfEnabled() + lw.lint.latex.root() if (lw.file.hasTexLangId(vscode.window.activeTextEditor?.document.languageId ?? '')) { prevTeXDocumentPath = vscode.window.activeTextEditor?.document.fileName } @@ -208,7 +202,7 @@ function registerLatexWorkshopCommands(extensionContext: vscode.ExtensionContext vscode.commands.registerCommand('latex-workshop.wordcount', () => lw.commands.wordcount()), vscode.commands.registerCommand('latex-workshop.log', () => lw.commands.showLog()), vscode.commands.registerCommand('latex-workshop.compilerlog', () => lw.commands.showLog('compiler')), - vscode.commands.registerCommand('latex-workshop.code-action', (d: vscode.TextDocument, r: vscode.Range, c: number, m: string) => lw.codeActions.runCodeAction(d, r, c, m)), + vscode.commands.registerCommand('latex-workshop.code-action', (d: vscode.TextDocument, r: vscode.Range, c: number, m: string) => lw.lint.latex.action(d, r, c, m)), vscode.commands.registerCommand('latex-workshop.goto-section', (filePath: string, lineNumber: number) => lw.commands.gotoSection(filePath, lineNumber)), vscode.commands.registerCommand('latex-workshop.navigate-envpair', () => lw.commands.navigateToEnvPair()), vscode.commands.registerCommand('latex-workshop.select-envcontent', () => lw.commands.selectEnvContent('content')), @@ -253,9 +247,9 @@ function registerLatexWorkshopCommands(extensionContext: vscode.ExtensionContext vscode.commands.registerCommand('latex-workshop.demote-sectioning', () => lw.commands.shiftSectioningLevel('demote')), vscode.commands.registerCommand('latex-workshop.select-section', () => lw.commands.selectSection()), - vscode.commands.registerCommand('latex-workshop.bibsort', () => bibtexFormat(true, false)), - vscode.commands.registerCommand('latex-workshop.bibalign', () => bibtexFormat(false, true)), - vscode.commands.registerCommand('latex-workshop.bibalignsort', () => bibtexFormat(true, true)), + vscode.commands.registerCommand('latex-workshop.bibsort', () => lw.lint.bibtex.format(true, false)), + vscode.commands.registerCommand('latex-workshop.bibalign', () => lw.lint.bibtex.format(false, true)), + vscode.commands.registerCommand('latex-workshop.bibalignsort', () => lw.lint.bibtex.format(true, true)), vscode.commands.registerCommand('latex-workshop.openMathPreviewPanel', () => lw.commands.openMathPreviewPanel()), vscode.commands.registerCommand('latex-workshop.closeMathPreviewPanel', () => lw.commands.closeMathPreviewPanel()), @@ -274,10 +268,10 @@ function registerProviders(extensionContext: vscode.ExtensionContext) { const bibtexSelector = selectDocumentsWithId(['bibtex']) extensionContext.subscriptions.push( - vscode.languages.registerDocumentFormattingEditProvider(latexindentSelector, latexFormatterProvider), - vscode.languages.registerDocumentFormattingEditProvider(bibtexSelector, bibtexFormatterProvider), - vscode.languages.registerDocumentRangeFormattingEditProvider(latexindentSelector, latexFormatterProvider), - vscode.languages.registerDocumentRangeFormattingEditProvider(bibtexSelector, bibtexFormatterProvider) + vscode.languages.registerDocumentFormattingEditProvider(latexindentSelector, lw.lint.latex.formatter), + vscode.languages.registerDocumentFormattingEditProvider(bibtexSelector, lw.lint.bibtex.formatter), + vscode.languages.registerDocumentRangeFormattingEditProvider(latexindentSelector, lw.lint.latex.formatter), + vscode.languages.registerDocumentRangeFormattingEditProvider(bibtexSelector, lw.lint.bibtex.formatter) ) extensionContext.subscriptions.push( @@ -337,7 +331,7 @@ function registerProviders(extensionContext: vscode.ExtensionContext) { }) extensionContext.subscriptions.push( - vscode.languages.registerCodeActionsProvider(latexSelector, lw.codeActions), + vscode.languages.registerCodeActionsProvider(latexSelector, lw.lint.latex.actionprovider), vscode.languages.registerFoldingRangeProvider(latexSelector, new FoldingProvider()), vscode.languages.registerFoldingRangeProvider(weaveSelector, new WeaveFoldingProvider()) ) diff --git a/src/types.ts b/src/types.ts index c873eeb0c..d0f0b3ab7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,11 @@ +import type * as vscode from 'vscode' import type * as Ast from '@unified-latex/unified-latex-types' import type { CmdEnvSuggestion } from './completion/completer/completerutils' import type { CiteSuggestion } from './completion/completer/citation' import type { GlossarySuggestion } from './completion/completer/glossary' import type { ICompletionItem } from './completion/latex' -export interface FileCache { +export type FileCache = { /** The raw file path of this Cache. */ filePath: string, /** Cached content of file. Dirty if opened in vscode, disk otherwise */ @@ -105,3 +106,11 @@ export type SyncTeXRecordToTeX = { line: number, column: number } + +export interface LaTeXLinter { + readonly linterDiagnostics: vscode.DiagnosticCollection, + getName(): string, + lintRootFile(rootPath: string): Promise, + lintFile(document: vscode.TextDocument): Promise, + parseLog(log: string, filePath?: string): void +} diff --git a/test/suites/08_linter.test.ts b/test/suites/08_linter.test.ts index 4ff89ed5a..b029c1f84 100644 --- a/test/suites/08_linter.test.ts +++ b/test/suites/08_linter.test.ts @@ -3,8 +3,8 @@ import * as path from 'path' import * as assert from 'assert' import { lw } from '../../src/lw' import * as test from './utils' -import { chkTeX } from '../../src/lint/linterlib/chktex' -import { laCheck } from '../../src/lint/linterlib/lacheck' +import { chkTeX } from '../../src/lint/latex-linter/chktex' +import { laCheck } from '../../src/lint/latex-linter/lacheck' suite('Linter test suite', () => { test.suite.name = path.basename(__filename).replace('.test.js', '') diff --git a/test/suites/utils.ts b/test/suites/utils.ts index 93a195cf1..33b4bef50 100644 --- a/test/suites/utils.ts +++ b/test/suites/utils.ts @@ -78,7 +78,7 @@ export async function reset() { lw.root.file.path = undefined lw.root.subfiles.path = undefined lw.completer.input.reset() - lw.dupLabelDetector.reset() + lw.lint.label.reset() lw.cache.reset() glob.sync('**/{**.tex,**.pdf,**.bib}', { cwd: getFixture() }).forEach(file => { try {fs.unlinkSync(path.resolve(getFixture(), file))} catch {} }) }