Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix forward SyncTex accuracy in internal browser by providing a range location indication alternative #4194

Merged
merged 8 commits into from
Apr 15, 2024
Merged
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1670,9 +1670,19 @@
},
"latex-workshop.synctex.indicator.enabled": {
"scope": "window",
"type": "boolean",
"default": true,
"markdownDescription": "Define the visibility of SyncTeX indicator (a red highlighting circle) after a forward SyncTeX in the PDF viewer."
"type": "string",
"enum": [
"disabled",
"spot",
"range"
],
"enumDescriptions": [
"Disable indicator.",
"Indicates a single specific spot with a red circle SyncTeX indicator.",
"Indicates a possible range with a blue rectangular SyncTeX indicator. (Valid when not using synctex.js)"
],
"default": "spot",
"markdownDescription": "Define the visibility and style of SyncTeX indicator after a forward SyncTeX in the PDF viewer."
},
"latex-workshop.synctex.afterBuild.enabled": {
"scope": "window",
Expand Down
115 changes: 107 additions & 8 deletions src/locate/synctex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as fs from 'fs'
import * as path from 'path'
import * as cs from 'cross-spawn'
import { lw } from '../lw'
import type { SyncTeXRecordToPDF, SyncTeXRecordToTeX } from '../types'
import type { SyncTeXRecordToPDF, SyncTeXRecordToPDFAll, SyncTeXRecordToTeX } from '../types'
import { syncTeXToPDF, syncTeXToTeX } from './synctex/worker'
import { replaceArgumentPlaceholders } from '../utils/utils'
import { isSameRealPath } from '../utils/pathnormalize'
Expand Down Expand Up @@ -61,6 +61,72 @@ function parseToPDF(result: string): SyncTeXRecordToPDF {
}
}

/**
* Parse the result of SyncTeX forward to PDF with a list.
*
* This function takes the result of SyncTeX forward to PDF as a string and
* parses it to extract page number, x-coordinate, y-coordinate, box-based
* coordinates (h, v, H, W), and whether the red indicator should be shown
* in the viewer.
*
* @param result - The result string of SyncTeX forward to PDF.
* @returns A SyncTeXRecordToPDFAllList object containing a list of records,
* with each record containing page number, x-coordinate, y-coordinate,
* h-coordinate, v-coordinate, H-coordinate, W-coordinate, and an indicator.
* @throws Error if there is a parsing error.
*/
function parseToPDFList(result: string): SyncTeXRecordToPDFAll[] {
const records: SyncTeXRecordToPDFAll[] = [];
let started = false;
let recordIndex = -1;

for (const line of result.split('\n')) {
if (line.includes('SyncTeX result begin')) {
started = true;
continue;
}

if (line.includes('SyncTeX result end')) {
break;
}

if (!started) {
continue;
}

const pos = line.indexOf(':');
if (pos < 0) {
continue;
}

const key = line.substring(0, pos);
const value = line.substring(pos + 1).trim();

if (key == 'Output') {
recordIndex += 1;
const record : SyncTeXRecordToPDFAll = { page: 0, x: 0, y: 0, h: 0, v: 0, W: 0, H: 0, indicator: true };
records[recordIndex] = record;
}

if (key === 'Page' || key === 'h' || key === 'v' || key === 'W' || key === 'H' || key === 'x' || key === 'y') {
const record = records[recordIndex];
if (record) {
if (key === 'Page') {
record['page'] = Number(value)
} else {
record[key] = Number(value);
}
}
}
}

if (recordIndex != -1) {
return records;
} else {
throw(new Error('parse error when parsing the result of synctex forward.'))
}
}

/**
* Parse the result of SyncTeX backward to TeX.
*
Expand Down Expand Up @@ -181,11 +247,30 @@ function toPDF(args?: {line: number, filePath: string}, forcedViewer: 'auto' | '
logger.logError('Forward SyncTeX failed.', e)
}
} else {
void callSyncTeXToPDF(line, character, filePath, pdfFile).then( (record) => {
let indicatorType: string;

const indicatorConfig = configuration.get('synctex.indicator.enabled')

if (typeof indicatorConfig === "boolean") {
// if configuration is boolean in previous version, then use fallback logic.
if (indicatorConfig) {
indicatorType = "spot"
} else {
indicatorType = "disabled"
}
} else if (typeof indicatorConfig === "string") {
// if configuration is enum, then use directly.
indicatorType = indicatorConfig;
} else {
throw new Error("Invalid configuration value for indicator enabled");
}

void callSyncTeXToPDF(line, character, filePath, pdfFile, indicatorType).then( (record) => {
if (pdfFile) {
void lw.viewer.locate(pdfFile, record)
}
})

}
}

Expand All @@ -200,12 +285,21 @@ function toPDF(args?: {line: number, filePath: string}, forcedViewer: 'auto' | '
* @param col - The character position (column) in the line.
* @param filePath - The path of the TeX file.
* @param pdfFile - The path of the PDF file.
* @returns A promise resolving to a SyncTeXRecordToPDF object.
* @param indicatorType - The type of the SyncTex indicator.
* @returns A promise resolving to a SyncTeXRecordToPDF object or a SyncTeXRecordToPDF[] object.
*/
function callSyncTeXToPDF(line: number, col: number, filePath: string, pdfFile: string): Thenable<SyncTeXRecordToPDF> {
function callSyncTeXToPDF(line: number, col: number, filePath: string, pdfFile: string, indicatorType: string): Thenable<SyncTeXRecordToPDF>;
function callSyncTeXToPDF(line: number, col: number, filePath: string, pdfFile: string, indicatorType: string): Thenable<SyncTeXRecordToPDFAll[]>;
function callSyncTeXToPDF(line: number, col: number, filePath: string, pdfFile: string, indicatorType: string): Thenable<SyncTeXRecordToPDF> | Thenable<SyncTeXRecordToPDFAll[]> {
const configuration = vscode.workspace.getConfiguration('latex-workshop')
const docker = configuration.get('docker.enabled')
const args = ['view', '-i', `${line}:${col + 1}:${docker ? path.basename(filePath): filePath}`, '-o', docker ? path.basename(pdfFile): pdfFile]

let args: string[]
if (indicatorType === 'range') {
args = ['view', '-i', `${line}:0:${docker ? path.basename(filePath): filePath}`, '-o', docker ? path.basename(pdfFile): pdfFile]
} else {
args = ['view', '-i', `${line}:${col + 1}:${docker ? path.basename(filePath): filePath}`, '-o', docker ? path.basename(pdfFile): pdfFile]
}

let command = configuration.get('synctex.path') as string
if (docker) {
Expand Down Expand Up @@ -236,15 +330,20 @@ function callSyncTeXToPDF(line: number, col: number, filePath: string, pdfFile:
logger.logError(`(${logTag}) Forward SyncTeX failed.`, err, stderr)
})

return new Promise( (resolve) => {
return new Promise<SyncTeXRecordToPDF | SyncTeXRecordToPDFAll[]>( (resolve) => {
proc.on('exit', exitCode => {
if (exitCode !== 0) {
logger.logError(`(${logTag}) Forward SyncTeX failed.`, exitCode, stderr)
} else {
resolve(parseToPDF(stdout))
if (indicatorType === 'range') {
resolve(parseToPDFList(stdout))
} else if (indicatorType === 'spot') {
resolve(parseToPDF(stdout))
}
// indicatorType === 'disabled' will do nothing, return directly
}
})
})
}) as Thenable<SyncTeXRecordToPDF> | Thenable<SyncTeXRecordToPDFAll[]>
}

/**
Expand Down
7 changes: 3 additions & 4 deletions src/preview/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as path from 'path'
import * as os from 'os'
import * as cs from 'cross-spawn'
import { lw } from '../lw'
import type { SyncTeXRecordToPDF, ViewerMode } from '../types'
import type { SyncTeXRecordToPDF, SyncTeXRecordToPDFAll, ViewerMode } from '../types'
import * as manager from './viewer/pdfviewermanager'
import { populate } from './viewer/pdfviewerpanel'

Expand Down Expand Up @@ -392,7 +392,7 @@ function getParams(): PdfViewerParams {
* @param pdfFile The path of a PDF file.
* @param record The position to be revealed.
*/
async function locate(pdfFile: string, record: SyncTeXRecordToPDF): Promise<void> {
async function locate(pdfFile: string, record: SyncTeXRecordToPDF | SyncTeXRecordToPDFAll[]): Promise<void> {
const pdfUri = vscode.Uri.file(pdfFile)
let clientSet = manager.getClients(pdfUri)
if (clientSet === undefined || clientSet.size === 0) {
Expand All @@ -407,8 +407,7 @@ async function locate(pdfFile: string, record: SyncTeXRecordToPDF): Promise<void
const needDelay = showInvisibleWebviewPanel(pdfUri)
for (const client of clientSet) {
setTimeout(() => {
const indicator = vscode.workspace.getConfiguration('latex-workshop').get('synctex.indicator.enabled') as boolean
client.send({type: 'synctex', data: {...record, indicator}})
client.send({type: 'synctex', data: record})
}, needDelay ? 200 : 0)
logger.log(`Try to synctex ${pdfFile}`)
}
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ export type SyncTeXRecordToTeX = {
column: number
}

export type SyncTeXRecordToPDFAll = SyncTeXRecordToPDF & {
h: number;
v: number;
W: number;
H: number;
}

export interface LaTeXLinter {
readonly linterDiagnostics: vscode.DiagnosticCollection,
getName(): string,
Expand Down
22 changes: 16 additions & 6 deletions types/latex-workshop-protocol-types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@

type SynctexData = {
page: number;
x: number;
y: number;
indicator: boolean;
}

type SynctexRangeData = SynctexData & {
h: number;
v: number;
W: number;
H: number;
}

export type ServerResponse = {
type: 'refresh'
} | {
type: 'synctex',
data: {
page: number,
x: number,
y: number,
indicator: boolean
}
data: SynctexData | SynctexRangeData[]
} | {
type: 'reload'
}
Expand Down
17 changes: 17 additions & 0 deletions viewer/latexworkshop.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ html[dir='rtl'] .findbar {
transform: translate(-50%, -50%);
}

@keyframes synctex-indicator-fadeOut {
0% {
background-color: rgba(0, 0, 255, 0.4);
}
100% {
background-color: rgba(0, 0, 255, 0);
}
}

.synctex-indicator-rect {
position: absolute;
z-index: 100000;
background-color: rgba(0, 0, 255, 0.5);
pointer-events: none;
animation: synctex-indicator-fadeOut 2s forwards;
}

#synctex-indicator.show {
transition: none;
opacity: 0.8;
Expand Down
Loading
Loading