Skip to content

Commit 9f2af8e

Browse files
committed
Add pandoc path lookup and optional setting
1 parent e0d80c5 commit 9f2af8e

File tree

3 files changed

+155
-20
lines changed

3 files changed

+155
-20
lines changed

apps/vscode/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,13 @@
11071107
"default": true,
11081108
"markdownDescription": "When using a venv or conda environment, prefer Quarto CLI installed with pip in that environment. This will override Quarto CLI in the `PATH`, but not an explicitly configured `#quarto.path#`."
11091109
},
1110+
"quarto.pandocPath": {
1111+
"order": 12,
1112+
"scope": "window",
1113+
"type": "string",
1114+
"default": "",
1115+
"markdownDescription": "A path to the `pandoc` executable. By default, the extension looks for pandoc in the `PATH`, but if set, will use the path specified instead."
1116+
},
11101117
"quarto.render.renderOnSave": {
11111118
"order": 12,
11121119
"scope": "window",

apps/vscode/src/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export async function activate(context: vscode.ExtensionContext) {
6161
workspaceFolder,
6262
// Look for quarto in the app root; this is where Positron installs it
6363
[path.join(vscode.env.appRoot, 'quarto', 'bin')],
64-
vscode.window.showWarningMessage
64+
vscode.window.showWarningMessage,
65+
vscode.workspace.getConfiguration("quarto").get("pandoc")
6566
);
6667
if (quartoContext.available) {
6768

packages/quarto-core/src/context.ts

Lines changed: 146 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface QuartoContext {
3535

3636
/**
3737
* Initialize a Quarto context.
38-
*
38+
*
3939
* @param quartoPath A path to a user-specified Quarto executable. If
4040
* supplied, this will be used in preference to other methods of detecting
4141
* Quarto.
@@ -44,14 +44,15 @@ export interface QuartoContext {
4444
* @param additionalSearchPaths Additional paths to search for Quarto. These will only be used if
4545
* Quarto is not found in the default locations or the system path.
4646
* @param showWarning A function to call to show a warning message.
47-
*
47+
*
4848
* @returns A Quarto context.
4949
*/
5050
export function initQuartoContext(
5151
quartoPath?: string,
5252
workspaceFolder?: string,
5353
additionalSearchPaths?: string[],
54-
showWarning?: (msg: string) => void
54+
showWarning?: (msg: string) => void,
55+
pandocPath?: string
5556
): QuartoContext {
5657
// default warning to log
5758
showWarning = showWarning || console.log;
@@ -80,20 +81,30 @@ export function initQuartoContext(
8081
// use cmd suffix for older versions of quarto on windows
8182
const windows = os.platform() == "win32";
8283
const useCmd = windows && semver.lte(quartoInstall.version, "1.1.162");
83-
let pandocPath = path.join(quartoInstall!.binPath, "tools", "pandoc");
84-
// more recent versions of quarto use architecture-specific tools dir,
85-
// if the pandocPath is not found then look in the requisite dir for this arch
86-
if (!windows && !fs.existsSync(pandocPath)) {
87-
pandocPath = path.join(
88-
path.dirname(pandocPath),
89-
isArm_64() ? "aarch64" : "x86_64",
90-
path.basename(pandocPath)
91-
);
84+
85+
// Look for pandoc in settings
86+
let pandocInstall: PandocInstallation | undefined;
87+
if (pandocPath) {
88+
if (!path.isAbsolute(pandocPath) && workspaceFolder) {
89+
pandocPath = path.join(workspaceFolder, pandocPath);
90+
}
91+
pandocInstall = detectUserSpecifiedPandoc(pandocPath, showWarning);
92+
}
93+
94+
// next look on the path
95+
if (!pandocInstall) {
96+
pandocInstall = detectPandoc("pandoc");
9297
}
98+
99+
// if still not found, scan for versions of quarto in known locations
100+
if (!pandocInstall) {
101+
pandocInstall = scanForPandoc(quartoInstall, additionalSearchPaths);
102+
}
103+
93104
return {
94105
available: true,
95106
...quartoInstall,
96-
pandocPath,
107+
pandocPath: pandocInstall?.binPath || "",
97108
workspaceDir: workspaceFolder,
98109
useCmd,
99110
runQuarto: (options: ExecFileSyncOptions, ...args: string[]) =>
@@ -104,7 +115,7 @@ export function initQuartoContext(
104115
),
105116
runPandoc: (options: ExecFileSyncOptions, ...args: string[]) =>
106117
execProgram(
107-
pandocPath,
118+
pandocInstall?.binPath || "",
108119
args,
109120
options
110121
),
@@ -114,7 +125,7 @@ export function initQuartoContext(
114125
}
115126
}
116127

117-
export function quartoContextUnavailable() : QuartoContext {
128+
export function quartoContextUnavailable(): QuartoContext {
118129
return {
119130
available: false,
120131
version: "",
@@ -179,8 +190,8 @@ function detectUserSpecifiedQuarto(
179190
if (!fs.statSync(quartoPath).isFile()) {
180191
showWarning(
181192
"Specified quarto executable is a directory not a file: '" +
182-
quartoPath +
183-
"'"
193+
quartoPath +
194+
"'"
184195
);
185196
return undefined;
186197
}
@@ -191,9 +202,9 @@ function detectUserSpecifiedQuarto(
191202

192203
/**
193204
* Scan for Quarto in known locations.
194-
*
205+
*
195206
* @param additionalSearchPaths Additional paths to search for Quarto (optional)
196-
*
207+
*
197208
* @returns A Quarto installation if found, otherwise undefined
198209
*/
199210
function scanForQuarto(additionalSearchPaths?: string[]): QuartoInstallation | undefined {
@@ -231,3 +242,119 @@ function scanForQuarto(additionalSearchPaths?: string[]): QuartoInstallation | u
231242

232243
return undefined;
233244
}
245+
246+
type PandocInstallation = {
247+
version: string;
248+
binPath: string;
249+
};
250+
251+
function detectPandoc(pandocPath: string): PandocInstallation | undefined {
252+
// detect version and paths (fall back to .cmd on windows if necessary)
253+
const windows = os.platform() == "win32";
254+
let version: string | undefined;
255+
const readPandocInfo = (bin: string) => {
256+
version = execProgram(bin, ["--version"]).substring(7, 10).trim();
257+
};
258+
try {
259+
readPandocInfo(pandocPath);
260+
} catch (e) {
261+
if (windows) {
262+
try {
263+
readPandocInfo(pandocPath + ".cmd");
264+
} catch (e) { /* */ }
265+
}
266+
}
267+
// return version if we have it
268+
if (version) {
269+
return {
270+
version,
271+
binPath: pandocPath,
272+
};
273+
} else {
274+
return undefined;
275+
}
276+
}
277+
278+
function detectUserSpecifiedPandoc(
279+
pandocPath: string,
280+
showWarning: (msg: string) => void
281+
): PandocInstallation | undefined {
282+
// validate that it exists
283+
if (!fs.existsSync(pandocPath)) {
284+
showWarning(
285+
"Unable to find specified pandoc executable: '" + pandocPath + "'"
286+
);
287+
return undefined;
288+
}
289+
290+
// validate that it is a file
291+
if (!fs.statSync(pandocPath).isFile()) {
292+
showWarning(
293+
"Specified pandoc executable is a directory not a file: '" +
294+
pandocPath +
295+
"'"
296+
);
297+
return undefined;
298+
}
299+
300+
// detect
301+
return detectPandoc(pandocPath);
302+
}
303+
304+
/**
305+
* Scan for Pandoc in known locations.
306+
*
307+
* @param quartoInstall QuartoInstall instance to use for search first
308+
* @param additionalSearchPaths Additional paths to search for Quarto (optional)
309+
*
310+
* @returns A Quarto installation if found, otherwise undefined
311+
*/
312+
function scanForPandoc(quartoInstall: QuartoInstallation, additionalSearchPaths?: string[]): PandocInstallation | undefined {
313+
const scanPaths: string[] = [];
314+
const windows = os.platform() === "win32"
315+
316+
let pandocPath = path.join(quartoInstall!.binPath, "tools", "pandoc");
317+
// more recent versions of quarto use architecture-specific tools dir,
318+
// if the pandocPath is not found then look in the requisite dir for this arch
319+
if (!windows && !fs.existsSync(pandocPath)) {
320+
pandocPath = path.join(
321+
path.dirname(pandocPath),
322+
isArm_64() ? "aarch64" : "x86_64",
323+
path.basename(pandocPath)
324+
);
325+
}
326+
scanPaths.push(pandocPath)
327+
328+
if (windows) {
329+
// scanPaths.push("C:\\Program Files\\Quarto\\bin");
330+
// const localAppData = process.env["LOCALAPPDATA"];
331+
// if (localAppData) {
332+
// scanPaths.push(path.join(localAppData, "Programs", "Quarto", "bin"));
333+
// }
334+
// scanPaths.push("C:\\Program Files\\RStudio\\bin\\quarto\\bin");
335+
} else if (os.platform() === "darwin") {
336+
// scanPaths.push("/Applications/quarto/bin/");
337+
// const home = process.env.HOME;
338+
// if (home) {
339+
// scanPaths.push(path.join(home, "Applications", "quarto", "bin"));
340+
// }
341+
// scanPaths.push("/Applications/RStudio.app/Contents/MacOS/quarto/bin");
342+
} else if (os.platform() === "linux") {
343+
// scanPaths.push("/opt/quarto/bin");
344+
// scanPaths.push("/usr/lib/rstudio/bin/quarto/bin");
345+
// scanPaths.push("/usr/lib/rstudio-server/bin/quarto/bin");
346+
}
347+
348+
if (additionalSearchPaths) {
349+
scanPaths.push(...additionalSearchPaths);
350+
}
351+
352+
for (const scanPath of scanPaths.filter(fs.existsSync)) {
353+
const install = detectPandoc(path.join(scanPath, "pandoc"));
354+
if (install) {
355+
return install;
356+
}
357+
}
358+
359+
return undefined
360+
}

0 commit comments

Comments
 (0)