From 4b955e98b0f180e65d6a5a6de7cb1af8f5dda321 Mon Sep 17 00:00:00 2001 From: James Yu Date: Tue, 12 Dec 2023 15:11:43 +0000 Subject: [PATCH] Modularize extra features --- src/compile/build.ts | 6 +- src/core/commands.ts | 22 +- src/extras/activity-bar.ts | 187 ++++++++--------- src/extras/cleaner.ts | 248 +++++++++++------------ src/extras/counter.ts | 237 ++++++++++------------ src/extras/index.ts | 19 ++ src/extras/math-preview-panel.ts | 337 ++++++++++++++++--------------- src/extras/section.ts | 290 +++++++++++++------------- src/extras/snippet-view.ts | 153 +++++++------- src/extras/texdoc.ts | 105 +++++----- src/extras/texroot.ts | 68 ++++--- src/lw.ts | 18 +- src/main.ts | 27 +-- src/outline/structure.ts | 6 +- src/preview/graphics.ts | 10 +- test/suites/10_cleaner.test.ts | 28 +-- 16 files changed, 858 insertions(+), 903 deletions(-) create mode 100644 src/extras/index.ts diff --git a/src/compile/build.ts b/src/compile/build.ts index 48c07f577..0585d7bb2 100644 --- a/src/compile/build.ts +++ b/src/compile/build.ts @@ -350,7 +350,7 @@ function handleRetryError(step: RecipeStep) { logger.log('Cleaning auxiliary files and retrying build after toolchain error.') queue.prepend(step) - void lw.cleaner.clean(step.rootFile).then(() => lw.event.fire(lw.event.AutoCleaned)) + void lw.extra.clean(step.rootFile).then(() => lw.event.fire(lw.event.AutoCleaned)) } /** @@ -365,7 +365,7 @@ function handleRetryError(step: RecipeStep) { function handleNoRetryError(configuration: vscode.WorkspaceConfiguration, step: RecipeStep) { logger.refreshStatus('x', 'errorForeground') if (['onFailed', 'onBuilt'].includes(configuration.get('latex.autoClean.run') as string)) { - void lw.cleaner.clean(step.rootFile).then(() => lw.event.fire(lw.event.AutoCleaned)) + void lw.extra.clean(step.rootFile).then(() => lw.event.fire(lw.event.AutoCleaned)) } void logger.showErrorMessageWithCompilerLogButton('Recipe terminated with error.') queue.clear() @@ -424,7 +424,7 @@ async function afterSuccessfulBuilt(lastStep: Step, skipped: boolean) { } if (['onSucceeded', 'onBuilt'].includes(configuration.get('latex.autoClean.run') as string)) { logger.log('Auto Clean invoked.') - await lw.cleaner.clean(lastStep.rootFile) + await lw.extra.clean(lastStep.rootFile) lw.event.fire(lw.event.AutoCleaned) } } diff --git a/src/core/commands.ts b/src/core/commands.ts index 06198df7b..194cf7cc6 100644 --- a/src/core/commands.ts +++ b/src/core/commands.ts @@ -127,7 +127,7 @@ export async function clean(): Promise { return } } - return lw.cleaner.clean(pickedRootFile) + return lw.extra.clean(pickedRootFile) } export function addTexRoot() { @@ -135,7 +135,7 @@ export function addTexRoot() { if (!vscode.window.activeTextEditor || !lw.file.hasTexLangId(vscode.window.activeTextEditor.document.languageId)) { return } - lw.texMagician.addTexRoot() + lw.extra.texroot() } export function citation() { @@ -148,12 +148,12 @@ export function wordcount() { if (!vscode.window.activeTextEditor || !lw.file.hasTexLangId(vscode.window.activeTextEditor.document.languageId) || lw.root.file.path === vscode.window.activeTextEditor.document.fileName) { if (lw.root.file.path) { - lw.counter.count(lw.root.file.path) + lw.extra.count(lw.root.file.path, true, true) } else { logger.log('WORDCOUNT: No rootFile defined.') } } else { - lw.counter.count(vscode.window.activeTextEditor.document.fileName, false) + lw.extra.count(vscode.window.activeTextEditor.document.fileName, false, true) } } @@ -399,11 +399,11 @@ export async function toggleSelectedKeyword(keyword: string) { * @param change */ export function shiftSectioningLevel(change: 'promote' | 'demote') { - lw.section.shiftSectioningLevel(change) + lw.extra.section(change) } export function selectSection() { - lw.section.selectSection() + lw.extra.section('select') } export function devParseLog() { @@ -438,11 +438,11 @@ export async function devStripText() { } export function texdoc(packageName?: string) { - lw.texdoc.texdoc(packageName) + lw.extra.texdoc(packageName) } export function texdocUsepackages() { - lw.texdoc.texdocUsepackages() + lw.extra.texdoc(undefined, true) } export async function saveActive() { @@ -451,15 +451,15 @@ export async function saveActive() { } export function openMathPreviewPanel() { - return lw.mathPreviewPanel.open() + lw.extra.mathpreview.toggle('open') } export function closeMathPreviewPanel() { - lw.mathPreviewPanel.close() + lw.extra.mathpreview.toggle('close') } export function toggleMathPreviewPanel() { - lw.mathPreviewPanel.toggle() + lw.extra.mathpreview.toggle() } async function quickPickRootFile(rootFile: string, localRootFile: string, verb: string): Promise { diff --git a/src/extras/activity-bar.ts b/src/extras/activity-bar.ts index ae7b581a8..effee0f31 100644 --- a/src/extras/activity-bar.ts +++ b/src/extras/activity-bar.ts @@ -1,115 +1,89 @@ import * as vscode from 'vscode' import { lw } from '../lw' -export class LaTeXCommanderTreeView { - private readonly latexCommanderProvider: LaTeXCommanderProvider - - constructor() { - this.latexCommanderProvider = new LaTeXCommanderProvider() - vscode.window.createTreeView( - 'latex-workshop-commands', - { - treeDataProvider: this.latexCommanderProvider, - showCollapseAll: true - }) - } +lw.onConfigChange('latex.recipes', update) - update() { - this.latexCommanderProvider.update() +function buildNode(parent: LaTeXCommand, children: LaTeXCommand[]) { + if (children.length > 0) { + parent.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed + parent.children = children + children.forEach((c) => c.parent = parent) } + return parent } -class LaTeXCommanderProvider implements vscode.TreeDataProvider { - private readonly _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() - readonly onDidChangeTreeData: vscode.Event - private commands: LaTeXCommand[] = [] - - constructor() { - this.onDidChangeTreeData = this._onDidChangeTreeData.event - vscode.workspace.onDidChangeConfiguration((ev: vscode.ConfigurationChangeEvent) => { - if (ev.affectsConfiguration('latex-workshop.latex.recipes', lw.root.getWorkspace())) { - this.update() - } - }) - this.commands = this.buildCommandTree() - } +function buildCommandTree(): LaTeXCommand[] { + const commands: LaTeXCommand[] = [] + const configuration = vscode.workspace.getConfiguration('latex-workshop', lw.root.getWorkspace()) + + const buildCommand = new LaTeXCommand('Build LaTeX project', {command: 'latex-workshop.build'}, 'debug-start') + const recipes = configuration.get('latex.recipes', []) as {name: string}[] + const recipeCommands = recipes.map(recipe => new LaTeXCommand(`Recipe: ${recipe.name}`, {command: 'latex-workshop.recipes', arguments: [recipe.name]}, 'debug-start')) + let node: LaTeXCommand + node = buildNode(buildCommand, [ + new LaTeXCommand('Clean up auxiliary files', {command: 'latex-workshop.clean'}, 'clear-all'), + new LaTeXCommand('Terminate current compilation', {command: 'latex-workshop.kill'}, 'debug-stop'), + ...recipeCommands + ]) + commands.push(node) + + const viewCommand = new LaTeXCommand('View LaTeX PDF', {command: 'latex-workshop.view'}, 'open-preview') + node = buildNode(viewCommand, [ + new LaTeXCommand('View in VSCode tab', {command: 'latex-workshop.view', arguments: ['tab']}, 'open-preview'), + new LaTeXCommand('View in web browser', {command: 'latex-workshop.view', arguments: ['browser']}, 'browser'), + new LaTeXCommand('View in external viewer', {command: 'latex-workshop.view', arguments: ['external']}, 'preview'), + new LaTeXCommand('Refresh all viewers', {command: 'latex-workshop.refresh-viewer'}, 'refresh') + ]) + commands.push(node) + + const logCommand = new LaTeXCommand('View Log messages', {command: 'latex-workshop.log'}, 'output') + const compilerLog = new LaTeXCommand('View LaTeX compiler log', {command: 'latex-workshop.compilerlog'}, 'output') + const latexWorkshopLog = new LaTeXCommand('View LaTeX Workshop extension log', {command: 'latex-workshop.log'}, 'output') + node = buildNode(logCommand, [ + latexWorkshopLog, + compilerLog + ]) + commands.push(node) + + const navCommand = new LaTeXCommand('Navigate, select, and edit', undefined, 'edit') + node= buildNode(navCommand, [ + new LaTeXCommand('SyncTeX from cursor', {command: 'latex-workshop.synctex'}, 'go-to-file'), + new LaTeXCommand('Navigate to matching begin/end', {command: 'latex-workshop.navigate-envpair'}), + new LaTeXCommand('Select current environment content', {command: 'latex-workshop.select-envcontent'}), + new LaTeXCommand('Select current environment name', {command: 'latex-workshop.select-envname'}), + new LaTeXCommand('Close current environment', {command: 'latex-workshop.close-env'}), + new LaTeXCommand('Surround with begin{}...\\end{}', {command: 'latex-workshop.wrap-env'}), + new LaTeXCommand('Insert %!TeX root magic comment', {command: 'latex-workshop.addtexroot'}) + ]) + commands.push(node) + + const miscCommand = new LaTeXCommand('Miscellaneous', undefined, 'menu') + node = buildNode(miscCommand, [ + new LaTeXCommand('Open citation browser', {command: 'latex-workshop.citation'}), + new LaTeXCommand('Count words in LaTeX project', {command: 'latex-workshop.wordcount'}), + new LaTeXCommand('Reveal output folder in OS', {command: 'latex-workshop.revealOutputDir'}, 'folder-opened') + ]) + commands.push(node) + + const bibtexCommand = new LaTeXCommand('BibTeX actions', undefined, 'references') + node = buildNode(bibtexCommand, [ + new LaTeXCommand('Align bibliography', {command: 'latex-workshop.bibalign'}), + new LaTeXCommand('Sort bibliography', {command: 'latex-workshop.bibsort'}, 'sort-precedence'), + new LaTeXCommand('Align and sort bibliography', {command: 'latex-workshop.bibalignsort'}) + ]) + commands.push(node) + return commands +} - update() { - this.commands = this.buildCommandTree() - this._onDidChangeTreeData.fire(undefined) - } - private buildNode(parent: LaTeXCommand, children: LaTeXCommand[]) { - if (children.length > 0) { - parent.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed - parent.children = children - children.forEach((c) => c.parent = parent) - } - return parent - } +function update() { + state.commands = buildCommandTree() + state.treeDataProvider._onDidChangeTreeData.fire(undefined) +} - private buildCommandTree(): LaTeXCommand[] { - const commands: LaTeXCommand[] = [] - const configuration = vscode.workspace.getConfiguration('latex-workshop', lw.root.getWorkspace()) - - const buildCommand = new LaTeXCommand('Build LaTeX project', {command: 'latex-workshop.build'}, 'debug-start') - const recipes = configuration.get('latex.recipes', []) as {name: string}[] - const recipeCommands = recipes.map(recipe => new LaTeXCommand(`Recipe: ${recipe.name}`, {command: 'latex-workshop.recipes', arguments: [recipe.name]}, 'debug-start')) - let node: LaTeXCommand - node = this.buildNode(buildCommand, [ - new LaTeXCommand('Clean up auxiliary files', {command: 'latex-workshop.clean'}, 'clear-all'), - new LaTeXCommand('Terminate current compilation', {command: 'latex-workshop.kill'}, 'debug-stop'), - ...recipeCommands - ]) - commands.push(node) - - const viewCommand = new LaTeXCommand('View LaTeX PDF', {command: 'latex-workshop.view'}, 'open-preview') - node = this.buildNode(viewCommand, [ - new LaTeXCommand('View in VSCode tab', {command: 'latex-workshop.view', arguments: ['tab']}, 'open-preview'), - new LaTeXCommand('View in web browser', {command: 'latex-workshop.view', arguments: ['browser']}, 'browser'), - new LaTeXCommand('View in external viewer', {command: 'latex-workshop.view', arguments: ['external']}, 'preview'), - new LaTeXCommand('Refresh all viewers', {command: 'latex-workshop.refresh-viewer'}, 'refresh') - ]) - commands.push(node) - - const logCommand = new LaTeXCommand('View Log messages', {command: 'latex-workshop.log'}, 'output') - const compilerLog = new LaTeXCommand('View LaTeX compiler log', {command: 'latex-workshop.compilerlog'}, 'output') - const latexWorkshopLog = new LaTeXCommand('View LaTeX Workshop extension log', {command: 'latex-workshop.log'}, 'output') - node = this.buildNode(logCommand, [ - latexWorkshopLog, - compilerLog - ]) - commands.push(node) - - const navCommand = new LaTeXCommand('Navigate, select, and edit', undefined, 'edit') - node= this.buildNode(navCommand, [ - new LaTeXCommand('SyncTeX from cursor', {command: 'latex-workshop.synctex'}, 'go-to-file'), - new LaTeXCommand('Navigate to matching begin/end', {command: 'latex-workshop.navigate-envpair'}), - new LaTeXCommand('Select current environment content', {command: 'latex-workshop.select-envcontent'}), - new LaTeXCommand('Select current environment name', {command: 'latex-workshop.select-envname'}), - new LaTeXCommand('Close current environment', {command: 'latex-workshop.close-env'}), - new LaTeXCommand('Surround with begin{}...\\end{}', {command: 'latex-workshop.wrap-env'}), - new LaTeXCommand('Insert %!TeX root magic comment', {command: 'latex-workshop.addtexroot'}) - ]) - commands.push(node) - - const miscCommand = new LaTeXCommand('Miscellaneous', undefined, 'menu') - node = this.buildNode(miscCommand, [ - new LaTeXCommand('Open citation browser', {command: 'latex-workshop.citation'}), - new LaTeXCommand('Count words in LaTeX project', {command: 'latex-workshop.wordcount'}), - new LaTeXCommand('Reveal output folder in OS', {command: 'latex-workshop.revealOutputDir'}, 'folder-opened') - ]) - commands.push(node) - - const bibtexCommand = new LaTeXCommand('BibTeX actions', undefined, 'references') - node = this.buildNode(bibtexCommand, [ - new LaTeXCommand('Align bibliography', {command: 'latex-workshop.bibalign'}), - new LaTeXCommand('Sort bibliography', {command: 'latex-workshop.bibsort'}, 'sort-precedence'), - new LaTeXCommand('Align and sort bibliography', {command: 'latex-workshop.bibalignsort'}) - ]) - commands.push(node) - return commands - } +class CommandProvider implements vscode.TreeDataProvider { + readonly _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event getTreeItem(element: LaTeXCommand): vscode.TreeItem { const treeItem: vscode.TreeItem = new vscode.TreeItem(element.label, element.collapsibleState) @@ -120,7 +94,7 @@ class LaTeXCommanderProvider implements vscode.TreeDataProvider { getChildren(element?: LaTeXCommand): LaTeXCommand[] { if (!element) { - return this.commands + return state.commands } return element.children @@ -147,3 +121,10 @@ class LaTeXCommand { } } } + +const treeDataProvider = new CommandProvider() +const state = { + commands: buildCommandTree(), + view: vscode.window.createTreeView('latex-workshop-commands', { treeDataProvider, showCollapseAll: true }), + treeDataProvider +} diff --git a/src/extras/cleaner.ts b/src/extras/cleaner.ts index 493dfc1c3..e3d8b2182 100644 --- a/src/extras/cleaner.ts +++ b/src/extras/cleaner.ts @@ -6,9 +6,12 @@ import * as cs from 'cross-spawn' import { lw } from '../lw' import { replaceArgumentPlaceholders } from '../utils/utils' - const logger = lw.log('Cleaner') +export { + clean +} + /** * Removes the duplicate elements. Note that the order of the sequence will not be preserved. */ @@ -31,148 +34,145 @@ function globAll(globs: string[], cwd: string): string[] { ).sort((a, b) => b.localeCompare(a)) } -export class Cleaner { - - async clean(rootFile?: string): Promise { - if (!rootFile) { - if (lw.root.file.path !== undefined) { - await lw.root.find() - } - rootFile = lw.root.file.path - if (!rootFile) { - logger.log('Cannot determine the root file to be cleaned.') - return - } +async function clean(rootFile?: string): Promise { + if (!rootFile) { + if (lw.root.file.path !== undefined) { + await lw.root.find() } - const configuration = vscode.workspace.getConfiguration('latex-workshop', vscode.Uri.file(rootFile)) - const cleanMethod = configuration.get('latex.clean.method') as string - switch (cleanMethod) { - case 'glob': - return this.cleanGlob(rootFile) - case 'cleanCommand': - await configuration.update('latex.clean.method', 'command') - void vscode.window.showInformationMessage('The cleaning method `cleanCommand` has been renamed to `command`. Your config is auto-updated.') - return this.cleanCommand(rootFile) - case 'command': - return this.cleanCommand(rootFile) - default: - logger.log(`Unknown cleaning method ${cleanMethod} .`) - return + rootFile = lw.root.file.path + if (!rootFile) { + logger.log('Cannot determine the root file to be cleaned.') + return } } + const configuration = vscode.workspace.getConfiguration('latex-workshop', vscode.Uri.file(rootFile)) + const cleanMethod = configuration.get('latex.clean.method') as string + switch (cleanMethod) { + case 'glob': + return cleanGlob(rootFile) + case 'cleanCommand': + await configuration.update('latex.clean.method', 'command') + void vscode.window.showInformationMessage('The cleaning method `cleanCommand` has been renamed to `command`. Your config is auto-updated.') + return cleanCommand(rootFile) + case 'command': + return cleanCommand(rootFile) + default: + logger.log(`Unknown cleaning method ${cleanMethod} .`) + return + } +} - /** - * Splits the given glob patterns into three distinct groups (duplicates will be ignored) - * 1. file or folder globs (not end with tailing slashes) - * 2. globs explicitly for folders - * 3. folder globs with globstar (`**`) - * - * We will remove the <1.> type paths if they are files, remove the <2.> type - * paths if they are empty folders, and ignore the <3.> type paths. - * - * @param globs a list of glob patterns - * @returns three distinct groups of glob patterns - */ - private static splitGlobs(globs: string[]): { fileOrFolderGlobs: string[], folderGlobsExplicit: string[], folderGlobsWithGlobstar: string[] } { - const fileOrFolderGlobs: string[] = [] - const folderGlobsWithGlobstar: string[] = [] - const folderGlobsExplicit: string[] = [] +/** + * Splits the given glob patterns into three distinct groups (duplicates will be ignored) + * 1. file or folder globs (not end with tailing slashes) + * 2. globs explicitly for folders + * 3. folder globs with globstar (`**`) + * + * We will remove the <1.> type paths if they are files, remove the <2.> type + * paths if they are empty folders, and ignore the <3.> type paths. + * + * @param globs a list of glob patterns + * @returns three distinct groups of glob patterns + */ +function splitGlobs(globs: string[]): { fileOrFolderGlobs: string[], folderGlobsExplicit: string[], folderGlobsWithGlobstar: string[] } { + const fileOrFolderGlobs: string[] = [] + const folderGlobsWithGlobstar: string[] = [] + const folderGlobsExplicit: string[] = [] - for (const pattern of unique(globs)) { - if (pattern.endsWith(path.sep)) { - if (path.basename(pattern).includes('**')) { - folderGlobsWithGlobstar.push(pattern) - } else { - folderGlobsExplicit.push(pattern) - } + for (const pattern of unique(globs)) { + if (pattern.endsWith(path.sep)) { + if (path.basename(pattern).includes('**')) { + folderGlobsWithGlobstar.push(pattern) } else { - fileOrFolderGlobs.push(pattern) + folderGlobsExplicit.push(pattern) } + } else { + fileOrFolderGlobs.push(pattern) } - - return { fileOrFolderGlobs, folderGlobsExplicit, folderGlobsWithGlobstar } } - /** - * Removes files in `outDir` matching the glob patterns. - * - * Also removes empty folders explicitly specified by the glob pattern. We - * only remove folders that are empty and the folder glob pattern is added - * intentionally by the user. Otherwise, the folders will be ignored. - */ - private async cleanGlob(rootFile: string): Promise { - const configuration = vscode.workspace.getConfiguration('latex-workshop', vscode.Uri.file(rootFile)) - const globPrefix = (configuration.get('latex.clean.subfolder.enabled') as boolean) ? './**/' : '' - const globs = (configuration.get('latex.clean.fileTypes') as string[]) - .map(globType => globPrefix + replaceArgumentPlaceholders(rootFile, lw.file.tmpDirPath)(globType)) - const outdir = path.resolve(path.dirname(rootFile), lw.file.getOutDir(rootFile)) - logger.log(`Clean glob matched files ${JSON.stringify({globs, outdir})} .`) + return { fileOrFolderGlobs, folderGlobsExplicit, folderGlobsWithGlobstar } +} - const { fileOrFolderGlobs, folderGlobsExplicit, folderGlobsWithGlobstar } = Cleaner.splitGlobs(globs) - logger.log(`Ignore folder glob patterns with globstar: ${folderGlobsWithGlobstar} .`) +/** + * Removes files in `outDir` matching the glob patterns. + * + * Also removes empty folders explicitly specified by the glob pattern. We + * only remove folders that are empty and the folder glob pattern is added + * intentionally by the user. Otherwise, the folders will be ignored. + */ +async function cleanGlob(rootFile: string): Promise { + const configuration = vscode.workspace.getConfiguration('latex-workshop', vscode.Uri.file(rootFile)) + const globPrefix = (configuration.get('latex.clean.subfolder.enabled') as boolean) ? './**/' : '' + const globs = (configuration.get('latex.clean.fileTypes') as string[]) + .map(globType => globPrefix + replaceArgumentPlaceholders(rootFile, lw.file.tmpDirPath)(globType)) + const outdir = path.resolve(path.dirname(rootFile), lw.file.getOutDir(rootFile)) + logger.log(`Clean glob matched files ${JSON.stringify({globs, outdir})} .`) - const explicitFolders: string[] = globAll(folderGlobsExplicit, outdir) - const explicitFoldersSet: Set = new Set(explicitFolders) - const filesOrFolders: string[] = globAll(fileOrFolderGlobs, outdir).filter(file => !explicitFoldersSet.has(file)) + const { fileOrFolderGlobs, folderGlobsExplicit, folderGlobsWithGlobstar } = splitGlobs(globs) + logger.log(`Ignore folder glob patterns with globstar: ${folderGlobsWithGlobstar} .`) - // Remove files first - for (const realPath of filesOrFolders) { - try { - const stats: fs.Stats = fs.statSync(realPath) - if (stats.isFile()) { - await fs.promises.unlink(realPath) - logger.log(`Cleaning file ${realPath} .`) - } else if (stats.isDirectory()) { - logger.log(`Not removing folder that is not explicitly specified ${realPath} .`) - } else { - logger.log(`Not removing non-file ${realPath} .`) - } - } catch (err) { - logger.logError(`Failed cleaning path ${realPath} .`, err) + const explicitFolders: string[] = globAll(folderGlobsExplicit, outdir) + const explicitFoldersSet: Set = new Set(explicitFolders) + const filesOrFolders: string[] = globAll(fileOrFolderGlobs, outdir).filter(file => !explicitFoldersSet.has(file)) + + // Remove files first + for (const realPath of filesOrFolders) { + try { + const stats: fs.Stats = fs.statSync(realPath) + if (stats.isFile()) { + await fs.promises.unlink(realPath) + logger.log(`Cleaning file ${realPath} .`) + } else if (stats.isDirectory()) { + logger.log(`Not removing folder that is not explicitly specified ${realPath} .`) + } else { + logger.log(`Not removing non-file ${realPath} .`) } + } catch (err) { + logger.logError(`Failed cleaning path ${realPath} .`, err) } + } - // Then remove empty folders EXPLICITLY specified by the user - for (const folderRealPath of explicitFolders) { - try { - if (fs.readdirSync(folderRealPath).length === 0) { - await fs.promises.rmdir(folderRealPath) - logger.log(`Removing empty folder: ${folderRealPath} .`) - } else { - logger.log(`Not removing non-empty folder: ${folderRealPath} .`) - } - } catch (err) { - logger.logError(`Failed cleaning folder ${folderRealPath} .`, err) + // Then remove empty folders EXPLICITLY specified by the user + for (const folderRealPath of explicitFolders) { + try { + if (fs.readdirSync(folderRealPath).length === 0) { + await fs.promises.rmdir(folderRealPath) + logger.log(`Removing empty folder: ${folderRealPath} .`) + } else { + logger.log(`Not removing non-empty folder: ${folderRealPath} .`) } + } catch (err) { + logger.logError(`Failed cleaning folder ${folderRealPath} .`, err) } } +} - private cleanCommand(rootFile: string): Promise { - const configuration = vscode.workspace.getConfiguration('latex-workshop', vscode.Uri.file(rootFile)) - const command = configuration.get('latex.clean.command') as string - const args = (configuration.get('latex.clean.args') as string[]) - .map(arg => { return replaceArgumentPlaceholders(rootFile, lw.file.tmpDirPath)(arg) - // cleaner.ts specific tokens - .replace(/%TEX%/g, rootFile) - }) - logger.logCommand('Clean temporary files command', command, args) - return new Promise((resolve, _reject) => { - // issue #3679 #3687: spawning with `detached: true` causes latexmk from MiKTeX to fail on Windows when "install on-the-fly" is enabled - const proc = cs.spawn(command, args, {cwd: path.dirname(rootFile)}) - let stderr = '' - proc.stderr.on('data', newStderr => { - stderr += newStderr - }) - proc.on('error', err => { - logger.logError(`Failed running cleaning command ${command} .`, err, stderr) - resolve() - }) - proc.on('exit', exitCode => { - if (exitCode !== 0) { - logger.logError('The clean command failed.', exitCode, stderr) - } - resolve() - }) +function cleanCommand(rootFile: string): Promise { + const configuration = vscode.workspace.getConfiguration('latex-workshop', vscode.Uri.file(rootFile)) + const command = configuration.get('latex.clean.command') as string + const args = (configuration.get('latex.clean.args') as string[]) + .map(arg => { return replaceArgumentPlaceholders(rootFile, lw.file.tmpDirPath)(arg) + // cleaner.ts specific tokens + .replace(/%TEX%/g, rootFile) }) - } + logger.logCommand('Clean temporary files command', command, args) + return new Promise((resolve, _reject) => { + // issue #3679 #3687: spawning with `detached: true` causes latexmk from MiKTeX to fail on Windows when "install on-the-fly" is enabled + const proc = cs.spawn(command, args, {cwd: path.dirname(rootFile)}) + let stderr = '' + proc.stderr.on('data', newStderr => { + stderr += newStderr + }) + proc.on('error', err => { + logger.logError(`Failed running cleaning command ${command} .`, err, stderr) + resolve() + }) + proc.on('exit', exitCode => { + if (exitCode !== 0) { + logger.logError('The clean command failed.', exitCode, stderr) + } + resolve() + }) + }) } diff --git a/src/extras/counter.ts b/src/extras/counter.ts index 016e6e414..4b0c5a3d4 100644 --- a/src/extras/counter.ts +++ b/src/extras/counter.ts @@ -4,152 +4,139 @@ import * as path from 'path' import * as cs from 'cross-spawn' import { lw } from '../lw' - const logger = lw.log('Counter') -export class Counter { - private useDocker: boolean = false - private disableCountAfterSave: boolean = false - private autoRunEnabled: boolean = false - private autoRunInterval: number = 0 - private commandArgs: string[] = [] - private commandPath: string = '' - private texCountMessage: string = '' - private wordCount: string = '' - private readonly status: vscode.StatusBarItem - - constructor() { - // gotoLine status item has priority 100.5 and selectIndentation item has priority 100.4 - this.status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100.45) - this.loadConfiguration(this.getWorkspace()) - this.updateStatusVisibility() - lw.onConfigChange(['texcount', 'docker.enabled'], () => { - this.loadConfiguration(this.getWorkspace()) - this.updateStatusVisibility() - }) - vscode.window.onDidChangeActiveTextEditor((e: vscode.TextEditor | undefined) => { - if (e && lw.file.hasTexLangId(e.document.languageId)) { - this.loadConfiguration(e.document.uri) - this.updateStatusVisibility() - } else { - this.status.hide() - } - }) - } - - private getWorkspace(): vscode.ConfigurationScope | undefined { - if (vscode.window.activeTextEditor) { - return vscode.window.activeTextEditor.document.uri - } - return lw.root.getWorkspace() - } +export { + count +} - private loadConfiguration(scope: vscode.ConfigurationScope | undefined) { - const configuration = vscode.workspace.getConfiguration('latex-workshop', scope) - this.autoRunEnabled = (configuration.get('texcount.autorun') as string === 'onSave') - this.autoRunInterval = configuration.get('texcount.interval') as number - this.commandArgs = configuration.get('texcount.args') as string[] - this.commandPath = configuration.get('texcount.path') as string - this.useDocker = configuration.get('docker.enabled') as boolean - } +const state = { + useDocker: false, + disableCountAfterSave: false, + autoRunEnabled: false, + autoRunInterval: 0, + commandArgs: [] as string[], + commandPath: '', + texCountMessage: '', + wordCount: '', + // gotoLine status item has priority 100.5 and selectIndentation item has priority 100.4 + statusBar: vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100.45) +} +loadConfigs() - private updateStatusVisibility() { - if (this.autoRunEnabled) { - this.updateWordCount() - this.status.show() +lw.onConfigChange(['texcount', 'docker.enabled'], loadConfigs) +lw.onDispose({ dispose: () => { + vscode.window.onDidChangeActiveTextEditor((e: vscode.TextEditor | undefined) => { + if (e && lw.file.hasTexLangId(e.document.languageId)) { + loadConfigs(e.document.uri) } else { - this.status.hide() + state.statusBar.hide() } + }) +} }) + +function loadConfigs(scope?: vscode.ConfigurationScope | undefined) { + scope = scope ?? vscode.window.activeTextEditor?.document.uri ?? lw.root.getWorkspace() + const configuration = vscode.workspace.getConfiguration('latex-workshop', scope) + state.autoRunEnabled = (configuration.get('texcount.autorun') as string === 'onSave') + state.autoRunInterval = configuration.get('texcount.interval') as number + state.commandArgs = configuration.get('texcount.args') as string[] + state.commandPath = configuration.get('texcount.path') as string + state.useDocker = configuration.get('docker.enabled') as boolean + if (state.autoRunEnabled) { + updateWordCount() + state.statusBar.show() + } else { + state.statusBar.hide() } +} - private updateWordCount() { - if (this.wordCount === '') { - this.status.text = '' - this.status.tooltip = '' - } else { - this.status.text = this.wordCount + ' words' - this.status.tooltip = this.texCountMessage - } +function updateWordCount() { + if (state.wordCount === '') { + state.statusBar.text = '' + state.statusBar.tooltip = '' + } else { + state.statusBar.text = state.wordCount + ' words' + state.statusBar.tooltip = state.texCountMessage } +} - countOnSaveIfEnabled(file: string) { - if (!this.autoRunEnabled) { +function count(file: string, merge: boolean = true, manual: boolean = false) { + if (!manual) { + if (!state.autoRunEnabled) { return } - if (this.disableCountAfterSave) { - logger.log('Auto texcount is temporarily disabled during a second.') + if (state.disableCountAfterSave) { + logger.log('Auto texcount is temporarily disabled in favor of `texcount.interval`.') return } logger.log(`Auto texcount started on saving file ${file} .`) - this.disableCountAfterSave = true - setTimeout(() => this.disableCountAfterSave = false, this.autoRunInterval) - void this.runTeXCount(file).then(() => { - this.updateWordCount() + state.disableCountAfterSave = true + setTimeout(() => state.disableCountAfterSave = false, state.autoRunInterval) + void runTeXCount(file).then(() => { + updateWordCount() }) - } - - - count(file: string, merge: boolean = true) { - void this.runTeXCount(file, merge).then( () => { - void vscode.window.showInformationMessage(this.texCountMessage) + } else { + void runTeXCount(file, merge).then( () => { + void vscode.window.showInformationMessage(state.texCountMessage) }) } +} - runTeXCount(file: string, merge: boolean = true): Promise { - let command = this.commandPath - if (this.useDocker) { - logger.log('Use Docker to invoke the command.') - if (process.platform === 'win32') { - command = path.resolve(lw.extensionRoot, './scripts/texcount.bat') - } else { - command = path.resolve(lw.extensionRoot, './scripts/texcount') - fs.chmodSync(command, 0o755) - } - } - const args = Array.from(this.commandArgs) - if (merge && !args.includes('-merge')) { - args.push('-merge') +function runTeXCount(file: string, merge: boolean = true): Promise { + let command = state.commandPath + if (state.useDocker) { + logger.log('Use Docker to invoke the command.') + if (process.platform === 'win32') { + command = path.resolve(lw.extensionRoot, './scripts/texcount.bat') + } else { + command = path.resolve(lw.extensionRoot, './scripts/texcount') + fs.chmodSync(command, 0o755) } - args.push(path.basename(file)) - logger.logCommand('Count words using command.', command, args) - const proc = cs.spawn(command, args, {cwd: path.dirname(file)}) - proc.stdout.setEncoding('utf8') - proc.stderr.setEncoding('utf8') - - let stdout = '' - proc.stdout.on('data', newStdout => { - stdout += newStdout - }) - - let stderr = '' - proc.stderr.on('data', newStderr => { - stderr += newStderr - }) - - proc.on('error', err => { - logger.logError('Cannot count words.', err, stderr) - void logger.showErrorMessage('TeXCount failed. Please refer to LaTeX Workshop Output for details.') - }) - - return new Promise( resolve => { - proc.on('exit', exitCode => { - if (exitCode !== 0) { - logger.logError('Cannot count words', exitCode, stderr) - void logger.showErrorMessage('TeXCount failed. Please refer to LaTeX Workshop Output for details.') - } else { - const words = /Words in text: ([0-9]*)/g.exec(stdout) - const floats = /Number of floats\/tables\/figures: ([0-9]*)/g.exec(stdout) - if (words) { - let floatMsg = '' - if (floats && parseInt(floats[1]) > 0) { - floatMsg = `and ${floats[1]} float${parseInt(floats[1]) > 1 ? 's' : ''} (tables, figures, etc.) ` - } - this.wordCount = words[1] - this.texCountMessage = `There are ${words[1]} words ${floatMsg}in the ${merge ? 'LaTeX project' : 'opened LaTeX file'}.` - resolve(true) + } + const args = Array.from(state.commandArgs) + if (merge && !args.includes('-merge')) { + args.push('-merge') + } + args.push(path.basename(file)) + logger.logCommand('Count words using command.', command, args) + const proc = cs.spawn(command, args, {cwd: path.dirname(file)}) + proc.stdout.setEncoding('utf8') + proc.stderr.setEncoding('utf8') + + let stdout = '' + proc.stdout.on('data', newStdout => { + stdout += newStdout + }) + + let stderr = '' + proc.stderr.on('data', newStderr => { + stderr += newStderr + }) + + proc.on('error', err => { + logger.logError('Cannot count words.', err, stderr) + void logger.showErrorMessage('TeXCount failed. Please refer to LaTeX Workshop Output for details.') + }) + + return new Promise( resolve => { + proc.on('exit', exitCode => { + if (exitCode !== 0) { + logger.logError('Cannot count words', exitCode, stderr) + void logger.showErrorMessage('TeXCount failed. Please refer to LaTeX Workshop Output for details.') + } else { + const words = /Words in text: ([0-9]*)/g.exec(stdout) + const floats = /Number of floats\/tables\/figures: ([0-9]*)/g.exec(stdout) + if (words) { + let floatMsg = '' + if (floats && parseInt(floats[1]) > 0) { + floatMsg = `and ${floats[1]} float${parseInt(floats[1]) > 1 ? 's' : ''} (tables, figures, etc.) ` } + state.wordCount = words[1] + state.texCountMessage = `There are ${words[1]} words ${floatMsg}in the ${merge ? 'LaTeX project' : 'opened LaTeX file'}.` + resolve(true) } - }) + } }) - } + }) } diff --git a/src/extras/index.ts b/src/extras/index.ts new file mode 100644 index 000000000..9d45d7307 --- /dev/null +++ b/src/extras/index.ts @@ -0,0 +1,19 @@ +import { count } from './counter' +import { clean } from './cleaner' +import { texdoc } from './texdoc' +import { texroot } from './texroot' +import { section } from './section' +import * as commands from './activity-bar' +import * as snippet from './snippet-view' +import * as mathpreview from './math-preview-panel' + +export const extra = { + count, + clean, + texdoc, + texroot, + section, + commands, + snippet, + mathpreview +} diff --git a/src/extras/math-preview-panel.ts b/src/extras/math-preview-panel.ts index 0d0c75be6..f583cc28d 100644 --- a/src/extras/math-preview-panel.ts +++ b/src/extras/math-preview-panel.ts @@ -1,12 +1,16 @@ import * as vscode from 'vscode' import * as path from 'path' -import type { TeXMathEnv } from '../types' import { moveWebviewPanel } from '../utils/webview' import { lw } from '../lw' - +import type { TeXMathEnv } from '../types' const logger = lw.log('Preview', 'Math') +export { + serializer, + toggle +} + type UpdateEvent = { type: 'edit', event: vscode.TextDocumentChangeEvent @@ -20,201 +24,198 @@ function resourcesFolder(extensionRoot: string) { return vscode.Uri.file(folder) } -export class MathPreviewPanelSerializer implements vscode.WebviewPanelSerializer { +class MathPreviewPanelSerializer implements vscode.WebviewPanelSerializer { deserializeWebviewPanel(panel: vscode.WebviewPanel) { - lw.mathPreviewPanel.initializePanel(panel) + initializePanel(panel) panel.webview.options = { enableScripts: true, localResourceRoots: [resourcesFolder(lw.extensionRoot)] } - panel.webview.html = lw.mathPreviewPanel.getHtml(panel.webview) + panel.webview.html = getHtml(panel.webview) logger.log('Math preview panel: restored') return Promise.resolve() } - +} +const serializer = new MathPreviewPanelSerializer() + +const state = { + panel: undefined as vscode.WebviewPanel | undefined, + prevEditTime: 0, + prevDocumentUri: undefined as string | undefined, + prevCursorPosition: undefined as vscode.Position | undefined, + prevNewCommands: undefined as string | undefined, } -export class MathPreviewPanel { - private panel?: vscode.WebviewPanel - private prevEditTime = 0 - private prevDocumentUri?: string - private prevCursorPosition?: vscode.Position - private prevNewCommands?: string - private needCursor: boolean - - constructor() { - this.needCursor = vscode.workspace.getConfiguration('latex-workshop').get('mathpreviewpanel.cursor.enabled', false) - lw.onConfigChange('mathpreviewpanel.cursor.enabled', () => { - this.needCursor = vscode.workspace.getConfiguration('latex-workshop').get('mathpreviewpanel.cursor.enabled', false) - }) - } - - open() { - const activeDocument = vscode.window.activeTextEditor?.document - if (this.panel) { - if (!this.panel.visible) { - this.panel.reveal(undefined, true) - } - return +function open() { + const activeDocument = vscode.window.activeTextEditor?.document + if (state.panel) { + if (!state.panel.visible) { + state.panel.reveal(undefined, true) } - lw.preview.math.getColor() - const panel = vscode.window.createWebviewPanel( - 'latex-workshop-mathpreview', - 'Math Preview', - { viewColumn: vscode.ViewColumn.Active, preserveFocus: true }, - { - enableScripts: true, - localResourceRoots: [resourcesFolder(lw.extensionRoot)], - retainContextWhenHidden: true - } - ) - this.initializePanel(panel) - panel.webview.html = this.getHtml(panel.webview) - const configuration = vscode.workspace.getConfiguration('latex-workshop') - const editorGroup = configuration.get('mathpreviewpanel.editorGroup') as string - if (activeDocument) { - void moveWebviewPanel(panel, editorGroup) + return + } + lw.preview.math.getColor() + const panel = vscode.window.createWebviewPanel( + 'latex-workshop-mathpreview', + 'Math Preview', + { viewColumn: vscode.ViewColumn.Active, preserveFocus: true }, + { + enableScripts: true, + localResourceRoots: [resourcesFolder(lw.extensionRoot)], + retainContextWhenHidden: true } - logger.log('Math preview panel: opened') + ) + initializePanel(panel) + panel.webview.html = getHtml(panel.webview) + const configuration = vscode.workspace.getConfiguration('latex-workshop') + const editorGroup = configuration.get('mathpreviewpanel.editorGroup') as string + if (activeDocument) { + void moveWebviewPanel(panel, editorGroup) } + logger.log('Math preview panel: opened') +} - initializePanel(panel: vscode.WebviewPanel) { - const disposable = vscode.Disposable.from( - vscode.workspace.onDidChangeTextDocument( (event) => { - void lw.mathPreviewPanel.update({type: 'edit', event}) - }), - vscode.window.onDidChangeTextEditorSelection( (event) => { - void lw.mathPreviewPanel.update({type: 'selection', event}) - }) - ) - this.panel = panel - panel.onDidDispose(() => { - disposable.dispose() - this.clearCache() - this.panel = undefined - logger.log('Math preview panel: disposed') +function initializePanel(panel: vscode.WebviewPanel) { + const disposable = vscode.Disposable.from( + vscode.workspace.onDidChangeTextDocument( (event) => { + void update({type: 'edit', event}) + }), + vscode.window.onDidChangeTextEditorSelection( (event) => { + void update({type: 'selection', event}) }) - panel.onDidChangeViewState((ev) => { - if (ev.webviewPanel.visible) { - void this.update() - } - }) - panel.webview.onDidReceiveMessage(() => { - logger.log('Math preview panel: initialized') - void this.update() - }) - } + ) + state.panel = panel + panel.onDidDispose(() => { + disposable.dispose() + clearCache() + state.panel = undefined + logger.log('Math preview panel: disposed') + }) + panel.onDidChangeViewState((ev) => { + if (ev.webviewPanel.visible) { + void update() + } + }) + panel.webview.onDidReceiveMessage(() => { + logger.log('Math preview panel: initialized') + void update() + }) +} - close() { - this.panel?.dispose() - this.panel = undefined - this.clearCache() - logger.log('Math preview panel: closed') - } +function close() { + state.panel?.dispose() + state.panel = undefined + clearCache() + logger.log('Math preview panel: closed') +} - toggle() { - if (this.panel) { - this.close() +function toggle(action?: 'open' | 'close') { + if (action) { + if (action === 'open') { + open() } else { - this.open() + close() } + } else if (state.panel) { + close() + } else { + open() } +} - private clearCache() { - this.prevEditTime = 0 - this.prevDocumentUri = undefined - this.prevCursorPosition = undefined - this.prevNewCommands = undefined - } - - getHtml(webview: vscode.Webview) { - const jsPath = vscode.Uri.file(path.join(lw.extensionRoot, './resources/mathpreviewpanel/mathpreview.js')) - const jsPathSrc = webview.asWebviewUri(jsPath) - return ` - - - - - - - - -
- - ` - } +function clearCache() { + state.prevEditTime = 0 + state.prevDocumentUri = undefined + state.prevCursorPosition = undefined + state.prevNewCommands = undefined +} - async update(ev?: UpdateEvent) { - if (!this.panel || !this.panel.visible) { - return - } - if (!this.needCursor) { - if (ev?.type === 'edit') { - this.prevEditTime = Date.now() - } else if (ev?.type === 'selection') { - if (Date.now() - this.prevEditTime < 100) { - return - } +function getHtml(webview: vscode.Webview) { + const jsPath = vscode.Uri.file(path.join(lw.extensionRoot, './resources/mathpreviewpanel/mathpreview.js')) + const jsPathSrc = webview.asWebviewUri(jsPath) + return ` + + + + + + + + +
+ + ` +} + +async function update(ev?: UpdateEvent) { + if (!state.panel || !state.panel.visible) { + return + } + if (!vscode.workspace.getConfiguration('latex-workshop').get('mathpreviewpanel.cursor.enabled', false)) { + if (ev?.type === 'edit') { + state.prevEditTime = Date.now() + } else if (ev?.type === 'selection') { + if (Date.now() - state.prevEditTime < 100) { + return } } + } + const editor = vscode.window.activeTextEditor + const document = editor?.document + if (!editor || !document?.languageId || !lw.file.hasTexLangId(document.languageId)) { + clearCache() + return + } + const documentUri = document.uri.toString() + if (ev?.type === 'edit' && documentUri !== ev.event.document.uri.toString()) { + return + } + const position = editor.selection.active + const texMath = getTexMath(document, position) + if (!texMath) { + clearCache() + return state.panel.webview.postMessage({type: 'mathImage', src: '' }) + } + let cachedCommands: string | undefined + if ( position.line === state.prevCursorPosition?.line && documentUri === state.prevDocumentUri ) { + cachedCommands = state.prevNewCommands + } + if (vscode.workspace.getConfiguration('latex-workshop').get('mathpreviewpanel.cursor.enabled', false)) { + await renderCursor(document, texMath) + } + const result = await lw.preview.math.generateSVG(texMath, cachedCommands).catch(() => undefined) + if (!result) { return } + state.prevDocumentUri = documentUri + state.prevNewCommands = result.newCommands + state.prevCursorPosition = position + return state.panel.webview.postMessage({type: 'mathImage', src: result.svgDataUrl }) +} - async renderCursor(document: vscode.TextDocument, tex: TeXMathEnv) { - const s = await lw.preview.math.renderCursor(document, tex) - tex.texString = s +function getTexMath(document: vscode.TextDocument, position: vscode.Position) { + const texMath = lw.preview.math.findMath(document, position) + if (texMath) { + if (texMath.envname !== '$') { + return texMath + } + if (texMath.range.start.character !== position.character && texMath.range.end.character !== position.character) { + return texMath + } } + return +} +async function renderCursor(document: vscode.TextDocument, tex: TeXMathEnv) { + const s = await lw.preview.math.renderCursor(document, tex) + tex.texString = s } diff --git a/src/extras/section.ts b/src/extras/section.ts index 7b08c732a..6639f908b 100644 --- a/src/extras/section.ts +++ b/src/extras/section.ts @@ -4,177 +4,181 @@ import { stripCommentsAndVerbatim } from '../utils/utils' const logger = lw.log('Section') +export { + section +} + interface MatchSection { level: string, pos: vscode.Position } -export class Section { - private readonly levels: string[] = ['part', 'chapter', 'section', 'subsection', 'subsubsection', 'paragraph', 'subparagraph'] - private readonly upperLevels = Object.create(null) as {[key: string]: string} - private readonly lowerLevels = Object.create(null) as {[key: string]: string} - - constructor() { - for (let i = 0; i < this.levels.length; i++) { - const current = this.levels[i] - const upper = this.levels[Math.max(i - 1, 0)] - const lower = this.levels[Math.min(i + 1, this.levels.length - 1)] - this.upperLevels[current] = upper - this.lowerLevels[current] = lower - } +const levels: string[] = ['part', 'chapter', 'section', 'subsection', 'subsubsection', 'paragraph', 'subparagraph'] +const upperLevels = Object.create(null) as {[key: string]: string} +const lowerLevels = Object.create(null) as {[key: string]: string} + +for (let i = 0; i < levels.length; i++) { + const current = levels[i] + const upper = levels[Math.max(i - 1, 0)] + const lower = levels[Math.min(i + 1, levels.length - 1)] + upperLevels[current] = upper + lowerLevels[current] = lower +} + +function section(action: 'promote' | 'demote' | 'select') { + if (action === 'select') { + selectSection() + } else { + shiftSection(action) } +} +/** + * Shift the level sectioning in the selection by one (up or down) + * @param change 'promote' or 'demote' + */ +function shiftSection(change: 'promote' | 'demote') { + logger.log(`Calling shiftSectioningLevel with parameter: ${change}`) + if (change !== 'promote' && change !== 'demote') { + throw TypeError( + `Invalid value of function parameter 'change' (=${change})` + ) + } - /** - * Shift the level sectioning in the selection by one (up or down) - * @param change 'promote' or 'demote' - */ - shiftSectioningLevel(change: 'promote' | 'demote') { - logger.log(`Calling shiftSectioningLevel with parameter: ${change}`) - if (change !== 'promote' && change !== 'demote') { - throw TypeError( - `Invalid value of function parameter 'change' (=${change})` - ) - } + const editor = vscode.window.activeTextEditor + if (editor === undefined) { + return + } - const editor = vscode.window.activeTextEditor - if (editor === undefined) { - return + const replacer = ( + _match: string, + sectionName: string, + asterisk: string | undefined, + options: string | undefined, + contents: string + ) => { + if (change === 'promote') { + return '\\' + upperLevels[sectionName] + (asterisk ?? '') + (options ?? '') + contents + } else { + // if (change === 'demote') + return '\\' + lowerLevels[sectionName] + (asterisk ?? '') + (options ?? '') + contents } + } - const replacer = ( - _match: string, - sectionName: string, - asterisk: string | undefined, - options: string | undefined, - contents: string - ) => { - if (change === 'promote') { - return '\\' + this.upperLevels[sectionName] + (asterisk ?? '') + (options ?? '') + contents - } else { - // if (change === 'demote') - return '\\' + this.lowerLevels[sectionName] + (asterisk ?? '') + (options ?? '') + contents - } - } + // when supported, negative lookbehind at start would be nice --- (? { - if (success) { - editor.selections = newSelections - } - }) } - /** - * Find the first sectioning command above the current position - * - * @param levels the list of sectioning commands - * @param pos the current position in the document - * @param doc the text document - */ - private searchLevelUp(levels: string[], pos: vscode.Position, doc: vscode.TextDocument): MatchSection | undefined { - const range = new vscode.Range(new vscode.Position(0, 0), pos.with(undefined, doc.lineAt(pos.line).range.end.character)) - const content = stripCommentsAndVerbatim(doc.getText(range)).split('\n') - const pattern = '\\\\(' + levels.join('|') + ')\\*?(?:\\[.+?\\])?\\{.*?\\}' - const regex = new RegExp(pattern) - for (let i = pos.line; i >= 0; i -= 1) { - const res = content[i].match(regex) - if (res) { - return {level: res[1], pos: new vscode.Position(i, 0)} - } + void vscode.workspace.applyEdit(edit).then(success => { + if (success) { + editor.selections = newSelections } - return - } - + }) +} - /** - * Find the first sectioning command below the current position. - * Stop at \appendix or \end{document} - * - * @param levels the list of sectioning commands - * @param pos the current position in the document - * @param doc the text document - */ - private searchLevelDown(levels: string[], pos: vscode.Position, doc: vscode.TextDocument): vscode.Position { - const range = new vscode.Range(pos, new vscode.Position(doc.lineCount, 0)) - const content = stripCommentsAndVerbatim(doc.getText(range)).split('\n') - const pattern = '\\\\(?:(' + levels.join('|') + ')\\*?(?:\\[.+?\\])?\\{.*?\\})|appendix|\\\\end{document}' - const regex = new RegExp(pattern) - for (let i = 0; i < content.length; i += 1) { - const res = content[i].match(regex) - if (res) { - return new vscode.Position(i + pos.line - 1, Math.max(content[i-1].length - 1, 0)) - } +/** + * Find the first sectioning command above the current position + * + * @param pos the current position in the document + * @param doc the text document + */ +function searchLevelUp(pos: vscode.Position, doc: vscode.TextDocument): MatchSection | undefined { + const range = new vscode.Range(new vscode.Position(0, 0), pos.with(undefined, doc.lineAt(pos.line).range.end.character)) + const content = stripCommentsAndVerbatim(doc.getText(range)).split('\n') + const pattern = '\\\\(' + levels.join('|') + ')\\*?(?:\\[.+?\\])?\\{.*?\\}' + const regex = new RegExp(pattern) + for (let i = pos.line; i >= 0; i -= 1) { + const res = content[i].match(regex) + if (res) { + return {level: res[1], pos: new vscode.Position(i, 0)} } - // Return the end of file position - return new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length - 1) } + return +} - selectSection() { - logger.log('Calling selectSection.') - const editor = vscode.window.activeTextEditor - if (editor === undefined) { - return - } - const beginLevel = this.searchLevelUp(this.levels, editor.selection.anchor, editor.document) - if (!beginLevel) { - logger.log('Cannot find any section command above current line.') - return +/** + * Find the first sectioning command below the current position. + * Stop at \appendix or \end{document} + * + * @param levels the list of sectioning commands + * @param pos the current position in the document + * @param doc the text document + */ +function searchLevelDown(remainlevels: string[], pos: vscode.Position, doc: vscode.TextDocument): vscode.Position { + const range = new vscode.Range(pos, new vscode.Position(doc.lineCount, 0)) + const content = stripCommentsAndVerbatim(doc.getText(range)).split('\n') + const pattern = '\\\\(?:(' + remainlevels.join('|') + ')\\*?(?:\\[.+?\\])?\\{.*?\\})|appendix|\\\\end{document}' + const regex = new RegExp(pattern) + for (let i = 0; i < content.length; i += 1) { + const res = content[i].match(regex) + if (res) { + return new vscode.Position(i + pos.line - 1, Math.max(content[i-1].length - 1, 0)) } - const levelIndex = this.levels.indexOf(beginLevel.level) - const levels = this.levels.slice(0, levelIndex + 1) - const endPosition = this.searchLevelDown(levels, editor.selection.end, editor.document) - editor.selection = new vscode.Selection(beginLevel.pos, endPosition) } + // Return the end of file position + return new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length - 1) +} + +function selectSection() { + logger.log('Calling selectSection.') + const editor = vscode.window.activeTextEditor + if (editor === undefined) { + return + } + const beginLevel = searchLevelUp(editor.selection.anchor, editor.document) + if (!beginLevel) { + logger.log('Cannot find any section command above current line.') + return + } + const levelIndex = levels.indexOf(beginLevel.level) + const endPosition = searchLevelDown(levels.slice(0, levelIndex + 1), editor.selection.end, editor.document) + editor.selection = new vscode.Selection(beginLevel.pos, endPosition) } diff --git a/src/extras/snippet-view.ts b/src/extras/snippet-view.ts index 44345a78b..1dd329af1 100644 --- a/src/extras/snippet-view.ts +++ b/src/extras/snippet-view.ts @@ -1,9 +1,20 @@ import * as vscode from 'vscode' -import {readFileSync} from 'fs' +import { readFileSync } from 'fs' import * as path from 'path' import { lw } from '../lw' -import {replaceWebviewPlaceholders} from '../utils/webview' +import { replaceWebviewPlaceholders } from '../utils/webview' +export { + state, + render, + provider +} + +lw.onDispose({ dispose: () => { + vscode.window.onDidChangeActiveTextEditor(e => { + state.lastActiveTextEditor = lw.file.hasTexLangId(e?.document.languageId ?? '') ? e : undefined + }) +} }) type SnippetViewResult = RenderResult | { type: 'insertSnippet', @@ -16,111 +27,89 @@ type RenderResult = { data: string | undefined } -export class SnippetView { - readonly snippetViewProvider: SnippetViewProvider - - constructor() { - this.snippetViewProvider = new SnippetViewProvider() +async function render(pdfFileUri: vscode.Uri, opts: { height: number, width: number, pageNumber: number }): Promise { + if (!state.view?.webview) { + return } - - async renderPdf(pdfFileUri: vscode.Uri, opts: { height: number, width: number, pageNumber: number }): Promise { - const webview = this.snippetViewProvider.webviewView?.webview - if (!webview) { - return - } - const uri = webview.asWebviewUri(pdfFileUri).toString() - let disposable: { dispose: () => void } | undefined - const promise = new Promise((resolve) => { - disposable = this.snippetViewProvider.onDidReceiveMessage((e: SnippetViewResult) => { - if (e.type !== 'png') { - return - } - if (e.uri === uri) { - resolve(e) - } - }) - setTimeout(() => { - disposable?.dispose() - resolve(undefined) - }, 3000) - void webview.postMessage({ - type: 'pdf', - uri, - opts - }) + const uri = state.view.webview.asWebviewUri(pdfFileUri).toString() + let disposable: { dispose: () => void } | undefined + const promise = new Promise((resolve) => { + disposable = on((e: SnippetViewResult) => { + if (e.type !== 'png') { + return + } + if (e.uri === uri) { + resolve(e) + } }) - try { - const renderResult = await promise - return renderResult?.data - } finally { + setTimeout(() => { disposable?.dispose() - } + resolve(undefined) + }, 3000) + void state.view?.webview.postMessage({ + type: 'pdf', + uri, + opts + }) + }) + try { + const renderResult = await promise + return renderResult?.data + } finally { + disposable?.dispose() } } -class SnippetViewProvider implements vscode.WebviewViewProvider { - private view: vscode.WebviewView | undefined - private lastActiveTextEditor: vscode.TextEditor | undefined - private readonly cbSet = new Set<(e: SnippetViewResult) => void>() - - constructor() { - const editor = vscode.window.activeTextEditor - if (editor && lw.file.hasTexLangId(editor.document.languageId)) { - this.lastActiveTextEditor = editor - } - vscode.window.onDidChangeActiveTextEditor(textEditor => { - if (textEditor && lw.file.hasTexLangId(textEditor.document.languageId)) { - this.lastActiveTextEditor = textEditor - } - }) +function on(cb: (e: SnippetViewResult) => void) { + state.callbacks.add(cb) + return { + dispose: () => state.callbacks.delete(cb) } +} - get webviewView() { - return this.view +function receive(message: SnippetViewResult) { + if (message.type === 'insertSnippet') { + const editor = state.lastActiveTextEditor + if (editor) { + editor.insertSnippet(new vscode.SnippetString(message.snippet.replace(/\\\n/g, '\\n'))).then( + () => {}, + err => { + void vscode.window.showWarningMessage(`Unable to insert symbol, ${err}`) + } + ) + } else { + void vscode.window.showWarningMessage('Unable get document to insert symbol into') + } } +} +class SnippetViewProvider implements vscode.WebviewViewProvider { public resolveWebviewView(webviewView: vscode.WebviewView) { - this.view = webviewView + state.view = webviewView webviewView.webview.options = { enableScripts: true } webviewView.onDidDispose(() => { - this.view = undefined + state.view = undefined }) const webviewSourcePath = path.join(lw.extensionRoot, 'resources', 'snippetview', 'snippetview.html') let webviewHtml = readFileSync(webviewSourcePath, { encoding: 'utf8' }) - webviewHtml = replaceWebviewPlaceholders(webviewHtml, this.view.webview) + webviewHtml = replaceWebviewPlaceholders(webviewHtml, state.view.webview) webviewView.webview.html = webviewHtml webviewView.webview.onDidReceiveMessage((e: SnippetViewResult) => { - this.cbSet.forEach((cb) => void cb(e)) - this.messageReceive(e) + state.callbacks.forEach((cb) => void cb(e)) + receive(e) }) } +} - private messageReceive(message: SnippetViewResult) { - if (message.type === 'insertSnippet') { - const editor = this.lastActiveTextEditor - if (editor) { - editor.insertSnippet(new vscode.SnippetString(message.snippet.replace(/\\\n/g, '\\n'))).then( - () => {}, - err => { - void vscode.window.showWarningMessage(`Unable to insert symbol, ${err}`) - } - ) - } else { - void vscode.window.showWarningMessage('Unable get document to insert symbol into') - } - } - } - - onDidReceiveMessage(cb: (e: SnippetViewResult) => void) { - this.cbSet.add(cb) - return { - dispose: () => this.cbSet.delete(cb) - } - } +const provider = new SnippetViewProvider() +const state = { + view: undefined as vscode.WebviewView | undefined, + lastActiveTextEditor: lw.file.hasTexLangId(vscode.window.activeTextEditor?.document.languageId ?? '') ? vscode.window.activeTextEditor : undefined, + callbacks: new Set<(e: SnippetViewResult) => void>() } diff --git a/src/extras/texdoc.ts b/src/extras/texdoc.ts index 9a50abd89..4b377d11f 100644 --- a/src/extras/texdoc.ts +++ b/src/extras/texdoc.ts @@ -2,65 +2,59 @@ import * as vscode from 'vscode' import * as cs from 'cross-spawn' import { lw } from '../lw' - const logger = lw.log('TeXDoc') -export class TeXDoc { - private runTexdoc(packageName: string) { - const configuration = vscode.workspace.getConfiguration('latex-workshop') - const texdocPath = configuration.get('texdoc.path') as string - const texdocArgs = Array.from(configuration.get('texdoc.args') as string[]) - texdocArgs.push(packageName) - logger.logCommand('Run texdoc command', texdocPath, texdocArgs) - const proc = cs.spawn(texdocPath, texdocArgs) +export { + texdoc +} - let stdout = '' - proc.stdout.on('data', newStdout => { - stdout += newStdout - }) +function runTexdoc(packageName: string) { + const configuration = vscode.workspace.getConfiguration('latex-workshop') + const texdocPath = configuration.get('texdoc.path') as string + const texdocArgs = Array.from(configuration.get('texdoc.args') as string[]) + texdocArgs.push(packageName) + logger.logCommand('Run texdoc command', texdocPath, texdocArgs) + const proc = cs.spawn(texdocPath, texdocArgs) - let stderr = '' - proc.stderr.on('data', newStderr => { - stderr += newStderr - }) + let stdout = '' + proc.stdout.on('data', newStdout => { + stdout += newStdout + }) - proc.on('error', err => { - logger.log(`Cannot run texdoc: ${err.message}, ${stderr}`) - void logger.showErrorMessage('Texdoc failed. Please refer to LaTeX Workshop Output for details.') - }) + let stderr = '' + proc.stderr.on('data', newStderr => { + stderr += newStderr + }) - proc.on('exit', exitCode => { - if (exitCode !== 0) { - logger.logError(`Cannot find documentation for ${packageName}.`, exitCode) - void logger.showErrorMessage('Texdoc failed. Please refer to LaTeX Workshop Output for details.') + proc.on('error', err => { + logger.log(`Cannot run texdoc: ${err.message}, ${stderr}`) + void logger.showErrorMessage('Texdoc failed. Please refer to LaTeX Workshop Output for details.') + }) + + proc.on('exit', exitCode => { + if (exitCode !== 0) { + logger.logError(`Cannot find documentation for ${packageName}.`, exitCode) + void logger.showErrorMessage('Texdoc failed. Please refer to LaTeX Workshop Output for details.') + } else { + const regex = new RegExp(`(no documentation found)|(Documentation for ${packageName} could not be found)`) + if (stdout.match(regex) || stderr.match(regex)) { + logger.log(`Cannot find documentation for ${packageName}.`) + void logger.showErrorMessage(`Cannot find documentation for ${packageName}.`) } else { - const regex = new RegExp(`(no documentation found)|(Documentation for ${packageName} could not be found)`) - if (stdout.match(regex) || stderr.match(regex)) { - logger.log(`Cannot find documentation for ${packageName}.`) - void logger.showErrorMessage(`Cannot find documentation for ${packageName}.`) - } else { - logger.log(`Opening documentation for ${packageName}.`) - } + logger.log(`Opening documentation for ${packageName}.`) } - logger.log(`texdoc stdout: ${stdout}`) - logger.log(`texdoc stderr: ${stderr}`) - }) - } - - texdoc(packageName?: string) { - if (packageName) { - this.runTexdoc(packageName) - return } - void vscode.window.showInputBox({value: '', prompt: 'Package name'}).then(selectedPkg => { - if (!selectedPkg) { - return - } - this.runTexdoc(selectedPkg) - }) - } + logger.log(`texdoc stdout: ${stdout}`) + logger.log(`texdoc stderr: ${stderr}`) + }) +} - texdocUsepackages() { +function texdoc(packageName?: string, useonly = false) { + if (packageName) { + runTexdoc(packageName) + return + } + if (useonly) { const names: Set = new Set() for (const tex of lw.cache.getIncludedTeX()) { const content = lw.cache.get(tex) @@ -68,15 +62,22 @@ export class TeXDoc { if (!pkgs) { continue } - Object.keys(pkgs).forEach(packageName => names.add(packageName)) + Object.keys(pkgs).forEach(pkg => names.add(pkg)) } const packageNames = Array.from(new Set(names)) - const items: vscode.QuickPickItem[] = packageNames.map(packageName => ({ label: packageName })) + const items: vscode.QuickPickItem[] = packageNames.map(pkg => ({ label: pkg })) void vscode.window.showQuickPick(items).then(selectedPkg => { if (!selectedPkg) { return } - this.runTexdoc(selectedPkg.label) + runTexdoc(selectedPkg.label) + }) + } else { + void vscode.window.showInputBox({value: '', prompt: 'Package name'}).then(selectedPkg => { + if (!selectedPkg) { + return + } + runTexdoc(selectedPkg) }) } } diff --git a/src/extras/texroot.ts b/src/extras/texroot.ts index 9659a43a7..87688af0c 100644 --- a/src/extras/texroot.ts +++ b/src/extras/texroot.ts @@ -2,40 +2,42 @@ import * as vscode from 'vscode' import * as os from 'os' import * as path from 'path' -export class TeXMagician { - getFileName(file: string): string { - const segments = file.replace(/\\/g, '/').match(/([^/]+$)/) - if (segments) { - return segments[0] - } - return '' +export { + texroot +} + +function getFileName(file: string): string { + const segments = file.replace(/\\/g, '/').match(/([^/]+$)/) + if (segments) { + return segments[0] } + return '' +} - addTexRoot() { - // taken from here: https://github.com/DonJayamanne/listFilesVSCode/blob/master/src/extension.ts (MIT licensed, should be fine) - void vscode.workspace.findFiles('**/*.{tex}').then(files => { - const displayFiles = files.map(file => { - return { description: file.fsPath, label: this.getFileName(file.fsPath), filePath: file.fsPath } - }) - void vscode.window.showQuickPick(displayFiles).then(val => { - const editor = vscode.window.activeTextEditor - if (!(val && editor)) { - return - } - const relativePath = path.relative(path.dirname(editor.document.fileName), val.filePath) - const magicComment = `% !TeX root = ${relativePath}` - const line0 = editor.document.lineAt(0).text - const edits = [(line0.match(/^\s*%\s*!TeX root/gmi)) ? - vscode.TextEdit.replace(new vscode.Range(0, 0, 0, line0.length), magicComment) - : - vscode.TextEdit.insert(new vscode.Position(0, 0), magicComment + os.EOL) - ] - // Insert the text - const uri = editor.document.uri - const edit = new vscode.WorkspaceEdit() - edit.set(uri, edits) - void vscode.workspace.applyEdit(edit) - }) +function texroot() { + // taken from here: https://github.com/DonJayamanne/listFilesVSCode/blob/master/src/extension.ts (MIT licensed, should be fine) + void vscode.workspace.findFiles('**/*.{tex}').then(files => { + const displayFiles = files.map(file => { + return { description: file.fsPath, label: getFileName(file.fsPath), filePath: file.fsPath } }) - } + void vscode.window.showQuickPick(displayFiles).then(val => { + const editor = vscode.window.activeTextEditor + if (!(val && editor)) { + return + } + const relativePath = path.relative(path.dirname(editor.document.fileName), val.filePath) + const magicComment = `% !TeX root = ${relativePath}` + const line0 = editor.document.lineAt(0).text + const edits = [(line0.match(/^\s*%\s*!TeX root/gmi)) ? + vscode.TextEdit.replace(new vscode.Range(0, 0, 0, line0.length), magicComment) + : + vscode.TextEdit.insert(new vscode.Position(0, 0), magicComment + os.EOL) + ] + // Insert the text + const uri = editor.document.uri + const edit = new vscode.WorkspaceEdit() + edit.set(uri, edits) + void vscode.workspace.applyEdit(edit) + }) + }) } diff --git a/src/lw.ts b/src/lw.ts index 95ad1c85c..bb9549962 100644 --- a/src/lw.ts +++ b/src/lw.ts @@ -11,16 +11,9 @@ import type { locate } from './locate' import type { lint } from './lint' import type { outline } from './outline' import type { parse } from './parse' +import type { extra } from './extras' -import type { Cleaner } from './extras/cleaner' -import type { LaTeXCommanderTreeView } from './extras/activity-bar' -import type { Counter } from './extras/counter' -import type { MathPreviewPanel } from './extras/math-preview-panel' -import type { Section } from './extras/section' -import type { SnippetView } from './extras/snippet-view' -import type { TeXMagician } from './extras/texroot' import type { AtSuggestionCompleter, Completer } from './completion/latex' -import type { TeXDoc } from './extras/texdoc' import type * as commands from './core/commands' /* eslint-disable */ @@ -43,14 +36,7 @@ export const lw = { atSuggestionCompleter: Object.create(null) as AtSuggestionCompleter, lint: {} as typeof lint, outline: {} as typeof outline, - cleaner: Object.create(null) as Cleaner, - counter: Object.create(null) as Counter, - texdoc: Object.create(null) as TeXDoc, - texMagician: Object.create(null) as TeXMagician, - section: Object.create(null) as Section, - latexCommanderTreeView: Object.create(null) as LaTeXCommanderTreeView, - snippetView: Object.create(null) as SnippetView, - mathPreviewPanel: Object.create(null) as MathPreviewPanel, + extra: {} as typeof extra, commands: Object.create(null) as typeof commands, onConfigChange, onDispose diff --git a/src/main.ts b/src/main.ts index 2287d2719..0f7d77725 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,8 +28,9 @@ import { lint } from './lint' lw.lint = lint import { outline } from './outline' lw.outline = outline +import { extra } from './extras' +lw.extra = extra -import { MathPreviewPanelSerializer } from './extras/math-preview-panel' import { BibtexCompleter } from './completion/bibtex' import { DocSymbolProvider } from './language/symbol-document' import { ProjectSymbolProvider } from './language/symbol-project' @@ -37,15 +38,7 @@ import { DefinitionProvider } from './language/definition' import { FoldingProvider, WeaveFoldingProvider } from './language/folding' import { SelectionRangeProvider } from './language/selection' -import { Cleaner } from './extras/cleaner' -import { LaTeXCommanderTreeView } from './extras/activity-bar' -import { Counter } from './extras/counter' -import { MathPreviewPanel } from './extras/math-preview-panel' -import { Section } from './extras/section' -import { SnippetView } from './extras/snippet-view' -import { TeXMagician } from './extras/texroot' import { AtSuggestionCompleter, Completer } from './completion/latex' -import { TeXDoc } from './extras/texdoc' import { parser } from './parse/parser' import * as commander from './core/commands' @@ -55,14 +48,6 @@ function initialize(extensionContext: vscode.ExtensionContext) { lw.onDispose(undefined, extensionContext.subscriptions) lw.completer = new Completer() lw.atSuggestionCompleter = new AtSuggestionCompleter() - lw.cleaner = new Cleaner() - lw.counter = new Counter() - lw.texdoc = new TeXDoc() - lw.texMagician = new TeXMagician() - lw.section = new Section() - lw.latexCommanderTreeView = new LaTeXCommanderTreeView() - lw.snippetView = new SnippetView() - lw.mathPreviewPanel = new MathPreviewPanel() lw.commands = commander void parser.reset() @@ -105,7 +90,7 @@ export function activate(extensionContext: vscode.ExtensionContext) { logger.log(`onDidSaveTextDocument triggered: ${e.uri.toString(true)}`) lw.lint.latex.root() void lw.compile.autoBuild(e.fileName, 'onSave') - lw.counter.countOnSaveIfEnabled(e.fileName) + lw.extra.count(e.fileName) } // We don't check LaTeX ID as the reconstruct is handled by the Cacher. // We don't check BibTeX ID as the reconstruct is handled by the citation completer. @@ -280,7 +265,7 @@ function registerProviders(extensionContext: vscode.ExtensionContext) { extensionContext.subscriptions.push( vscode.window.registerWebviewPanelSerializer('latex-workshop-pdf', lw.viewer.serializer), vscode.window.registerCustomEditorProvider('latex-workshop-pdf-hook', lw.viewer.hook, {supportsMultipleEditorsPerDocument: true, webviewOptions: {retainContextWhenHidden: true}}), - vscode.window.registerWebviewPanelSerializer('latex-workshop-mathpreview', new MathPreviewPanelSerializer()) + vscode.window.registerWebviewPanelSerializer('latex-workshop-mathpreview', lw.extra.mathpreview.serializer) ) extensionContext.subscriptions.push( @@ -347,8 +332,8 @@ function registerProviders(extensionContext: vscode.ExtensionContext) { extensionContext.subscriptions.push( vscode.window.registerWebviewViewProvider( 'latex-workshop-snippet-view', - lw.snippetView.snippetViewProvider, - {webviewOptions: {retainContextWhenHidden: true}} + lw.extra.snippet.provider, + { webviewOptions: { retainContextWhenHidden: true } } ) ) } diff --git a/src/outline/structure.ts b/src/outline/structure.ts index e14a91241..2dd0d6050 100644 --- a/src/outline/structure.ts +++ b/src/outline/structure.ts @@ -42,13 +42,13 @@ async function refresh(fireChangedEvent: boolean = true) { } function reveal(e: vscode.TextEditorSelectionChangeEvent) { - if (!outline.follow || !state.viewer.visible) { + if (!outline.follow || !state.view.visible) { return } const line = e.selections[0].active.line const f = e.textEditor.document.fileName const currentNode = traverseSectionTree(state.structure, f, line) - return currentNode ? state.viewer.reveal(currentNode, {select: true}) : undefined + return currentNode ? state.view.reveal(currentNode, {select: true}) : undefined } /** @@ -143,6 +143,6 @@ const state = { cachedTeX: undefined as TeXElement[] | undefined, cachedBib: undefined as TeXElement[] | undefined, cachedDTX: undefined as TeXElement[] | undefined, - viewer: vscode.window.createTreeView('latex-workshop-structure', { treeDataProvider, showCollapseAll: true }), + view: vscode.window.createTreeView('latex-workshop-structure', { treeDataProvider, showCollapseAll: true }), treeDataProvider } diff --git a/src/preview/graphics.ts b/src/preview/graphics.ts index 94d54312d..fd1ac40ac 100644 --- a/src/preview/graphics.ts +++ b/src/preview/graphics.ts @@ -64,8 +64,8 @@ async function asMD(filePath: string, opts: { height: number, width: number, pag let msg = '$(error) Failed to render.' if (!vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath))) { msg = '$(warning) Cannot render a PDF file not in workspaces.' - } else if (!lw.snippetView.snippetViewProvider.webviewView) { - msg = '$(info) Please activate Snippet View to render the thumbnail of a PDF file.' + } else if (!lw.extra.snippet.state.view) { + msg = '$(info) Please activate LaTeX Workshop sidebar item to render the thumbnail of a PDF file.' } return new vscode.MarkdownString(msg, true) } @@ -78,19 +78,19 @@ async function renderPdfFileAsDataUrl(pdfFilePath: string, opts: { height: numbe const maxDataUrlLength = 99980 let scale = 1.5 let newOpts = { height: opts.height * scale , width: opts.width * scale, pageNumber: opts.pageNumber } - let dataUrl = await lw.snippetView.renderPdf(vscode.Uri.file(pdfFilePath), newOpts) + let dataUrl = await lw.extra.snippet.render(vscode.Uri.file(pdfFilePath), newOpts) if (!dataUrl || dataUrl.length < maxDataUrlLength) { return dataUrl } scale = 1 newOpts = { height: opts.height * scale , width: opts.width * scale, pageNumber: opts.pageNumber } - dataUrl = await lw.snippetView.renderPdf(vscode.Uri.file(pdfFilePath), newOpts) + dataUrl = await lw.extra.snippet.render(vscode.Uri.file(pdfFilePath), newOpts) if (!dataUrl || dataUrl.length < maxDataUrlLength) { return dataUrl } scale = Math.sqrt(maxDataUrlLength/dataUrl.length) / 1.2 newOpts = { height: opts.height * scale , width: opts.width * scale, pageNumber: opts.pageNumber } - dataUrl = await lw.snippetView.renderPdf(vscode.Uri.file(pdfFilePath), newOpts) + dataUrl = await lw.extra.snippet.render(vscode.Uri.file(pdfFilePath), newOpts) if (dataUrl && dataUrl.length >= maxDataUrlLength) { logger.log(`Data URL still too large: ${pdfFilePath}`) return diff --git a/test/suites/10_cleaner.test.ts b/test/suites/10_cleaner.test.ts index eff3851f4..98b2cc8b6 100644 --- a/test/suites/10_cleaner.test.ts +++ b/test/suites/10_cleaner.test.ts @@ -34,7 +34,7 @@ suite('Cleaner test suite', () => { {src: 'empty', dst: 'main.fls'}, {src: 'empty', dst: 'sub.aux'} ], {skipCache: true}) - await lw.cleaner.clean(path.resolve(fixture, 'main.tex')) + await lw.extra.clean(path.resolve(fixture, 'main.tex')) assert.ok(!fs.existsSync(path.resolve(fixture, 'main.aux'))) assert.ok(!fs.existsSync(path.resolve(fixture, 'main.fls'))) assert.ok(fs.existsSync(path.resolve(fixture, 'sub.aux'))) @@ -49,7 +49,7 @@ suite('Cleaner test suite', () => { {src: 'empty', dst: 'main.fls'}, {src: 'empty', dst: 'sub.aux'} ], {skipCache: true}) - await lw.cleaner.clean(path.resolve(fixture, 'main.tex')) + await lw.extra.clean(path.resolve(fixture, 'main.tex')) assert.ok(!fs.existsSync(path.resolve(fixture, 'main.aux'))) assert.ok(fs.existsSync(path.resolve(fixture, 'main.fls'))) assert.ok(!fs.existsSync(path.resolve(fixture, 'sub.aux'))) @@ -62,11 +62,11 @@ suite('Cleaner test suite', () => { {src: 'base.tex', dst: 'main.tex'}, {src: 'empty', dst: 'out/main.aux'} ], {skipCache: true}) - await lw.cleaner.clean(path.resolve(fixture, 'main.tex')) + await lw.extra.clean(path.resolve(fixture, 'main.tex')) assert.ok(fs.existsSync(path.resolve(fixture, 'out/main.aux'))) await vscode.workspace.getConfiguration('latex-workshop').update('latex.clean.subfolder.enabled', true) - await lw.cleaner.clean(path.resolve(fixture, 'main.tex')) + await lw.extra.clean(path.resolve(fixture, 'main.tex')) assert.ok(!fs.existsSync(path.resolve(fixture, 'out/main.aux'))) }) @@ -77,7 +77,7 @@ suite('Cleaner test suite', () => { {src: 'base.tex', dst: 'main.tex'}, {src: 'empty', dst: 'main.aux'} ], {skipCache: true}) - await lw.cleaner.clean() + await lw.extra.clean() assert.ok(!fs.existsSync(path.resolve(fixture, 'main.aux'))) }) @@ -88,7 +88,7 @@ suite('Cleaner test suite', () => { {src: 'base.tex', dst: 'main.tex'}, {src: 'empty', dst: 'aux_files/main.aux'} ], {skipCache: true}) - await lw.cleaner.clean() + await lw.extra.clean() assert.ok(!fs.existsSync(path.resolve(fixture, 'aux_files/main.aux'))) }) @@ -100,7 +100,7 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'invalid_cmd.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() // Clean up previous remainders to ensure next build to fail + await lw.extra.clean() // Clean up previous remainders to ensure next build to fail const cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') const result = await Promise.any([cleaned, test.sleep(1000)]) @@ -112,7 +112,7 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'base.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() + await lw.extra.clean() const cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') const result = await Promise.any([cleaned, test.sleep(1000)]) @@ -127,7 +127,7 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'invalid_cmd.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() // Clean up previous remainders to ensure next build to fail + await lw.extra.clean() // Clean up previous remainders to ensure next build to fail let cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') let result = await Promise.any([cleaned, test.sleep(1000)]) @@ -136,7 +136,7 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'base.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() // Clean up previous remainders to ensure next build to fail + await lw.extra.clean() // Clean up previous remainders to ensure next build to fail cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') result = await Promise.any([cleaned, test.sleep(1000)]) @@ -151,7 +151,7 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'invalid_cmd.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() // Clean up previous remainders to ensure next build to fail + await lw.extra.clean() // Clean up previous remainders to ensure next build to fail let cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') let result = await Promise.any([cleaned, test.sleep(1000)]) @@ -160,7 +160,7 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'base.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() + await lw.extra.clean() cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') result = await Promise.any([cleaned, test.sleep(1000)]) @@ -175,14 +175,14 @@ suite('Cleaner test suite', () => { await test.load(fixture, [ {src: 'invalid_cmd.tex', dst: 'main.tex'} ], {skipCache: true}) - await lw.cleaner.clean() // Clean up previous remainders to ensure next build to fail + await lw.extra.clean() // Clean up previous remainders to ensure next build to fail let cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') let result = await Promise.any([cleaned, test.sleep(1000)]) assert.ok(!result) await vscode.workspace.getConfiguration('latex-workshop').update('latex.autoBuild.cleanAndRetry.enabled', true) - await lw.cleaner.clean() + await lw.extra.clean() cleaned = test.wait(lw.event.AutoCleaned).then(() => true) await test.build(fixture, 'main.tex') result = await Promise.any([cleaned, test.sleep(1000)])