Skip to content

Commit

Permalink
Fix forward SyncTex accuracy in internal browser by providing a range…
Browse files Browse the repository at this point in the history
… location indication alternative (#4194)

* Add forward SyncTex with range location indication.

* Remain default behavior of using red circle for forward SyncTex.

* Remove unused column parameter in callSyncTeXToPDFRange.

* Merged configuration `latex-workshop.synctex.indicator.type` to `latex-workshop.synctex.indicator.enabled`.

Added fallback logic in `toPDF` for boolean type of `latex-workshop.synctex.indicator.enabled`.

Extend type `SyncTeXRecordToPDFAll` from base type `SyncTeXRecordToPDF`.

Merged `locateRange` to `locate`.

Remove `SyncTeXRecordToPDFAllList`.

Merged `callSyncTeXToPDFRange` to `callSyncTeXToPDF`.

In `ServerResponse`, added and extended type `SynctexData` from base type `SynctexRangeData`.

* Merged `forwardSynctexRange` with `forwardSynctex` in viewer.

* Indent fixing.

* Change the enum name.

Simplify if-else clauses.

Change rectangle default color.

Add comments.

* Code style tweaks

---------

Co-authored-by: James Yu <[email protected]>
  • Loading branch information
pyk1998 and James-Yu authored Apr 15, 2024
1 parent 68c5e74 commit 2163ae2
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 60 deletions.
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1682,9 +1682,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": [
"none",
"circle",
"rectangle"
],
"enumDescriptions": [
"Hide the indicator.",
"Indicates a possible location with a red circular SyncTeX indicator.",
"Indicates the whole line selected in the Tex file with a red rectangular SyncTeX indicator. (Valid when not using synctex.js)"
],
"default": "circle",
"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
131 changes: 110 additions & 21 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 @@ -169,23 +235,38 @@ function toPDF(args?: {line: number, filePath: string}, forcedViewer: 'auto' | '

const useSyncTexJs = configuration.get('synctex.synctexjs.enabled') as boolean

if (useSyncTexJs) {
try {
logger.log(`Forward from ${filePath} to ${pdfFile} on line ${line}.`)
const record = syncTeXToPDF(line, filePath, pdfFile)
if (!record) {
return
}
void lw.viewer.locate(pdfFile, record)
} catch (e) {
logger.logError('Forward SyncTeX failed.', e)
}
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.
indicatorType = indicatorConfig ? 'circle' : 'none'
} else {
void callSyncTeXToPDF(line, character, filePath, pdfFile).then( (record) => {
if (pdfFile) {
// if configuration is enum, then use directly.
indicatorType = indicatorConfig as string
}

// guard if indicatorConfig is illegal or equals to 'none', display none.
if (indicatorType === 'circle' || indicatorType === 'rectangle') {
if (useSyncTexJs) {
try {
logger.log(`Forward from ${filePath} to ${pdfFile} on line ${line}.`)
const record = syncTeXToPDF(line, filePath, pdfFile)
if (!record) {
return
}
void lw.viewer.locate(pdfFile, record)
} catch (e) {
logger.logError('Forward SyncTeX failed.', e)
}
})
} else {
void callSyncTeXToPDF(line, character, filePath, pdfFile, indicatorType).then( (record) => {
if (pdfFile) {
void lw.viewer.locate(pdfFile, record)
}
})
}
}
}

Expand All @@ -200,12 +281,20 @@ 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]

const args = ['view', '-i'].concat([
`${line}${indicatorType === 'rectangle' ? ':0' : `:${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 +325,15 @@ 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))
resolve(indicatorType === 'rectangle' ? parseToPDFList(stdout) : parseToPDF(stdout))
}
})
})
}) 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 @@ -110,6 +110,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
20 changes: 20 additions & 0 deletions viewer/latexworkshop.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ html[dir='rtl'] .findbar {
transform: translate(-50%, -50%);
}

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

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

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

0 comments on commit 2163ae2

Please sign in to comment.