diff --git a/package/scripts/common/quarto b/package/scripts/common/quarto index 0585a391f46..b33439e697a 100755 --- a/package/scripts/common/quarto +++ b/package/scripts/common/quarto @@ -187,9 +187,9 @@ QUARTO_DENO_OPTIONS="--unstable-ffi --unstable-kv --no-config --no-lock ${QUARTO # --enable-experimental-regexp-engine is required for /regex/l, https://github.com/quarto-dev/quarto-cli/issues/9737 if [ "$QUARTO_DENO_V8_OPTIONS" != "" ]; then - QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192,${QUARTO_DENO_V8_OPTIONS}" + QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192,--stack-trace-limit=100,${QUARTO_DENO_V8_OPTIONS}" else - QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192" + QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192,--stack-trace-limit=100" fi if [ "$QUARTO_DENO_EXTRA_OPTIONS" == "" ]; then diff --git a/src/command/preview/cmd.ts b/src/command/preview/cmd.ts index 8effb9d3472..b37e8ea4c80 100644 --- a/src/command/preview/cmd.ts +++ b/src/command/preview/cmd.ts @@ -292,7 +292,7 @@ export const previewCommand = new Command() services.cleanup(); } })(); - const format = await previewFormat(file, flags.to, formats, project); + const format = await previewFormat(file, project, flags.to, formats); // see if this is server: shiny document and if it is then forward to previewShiny if (isHtmlOutput(parseFormatString(format).baseFormat)) { @@ -314,6 +314,7 @@ export const previewCommand = new Command() format, pandocArgs: args, watchInputs: options.watchInputs!, + project, }); exitWithCleanup(result.code); throw new Error(); // unreachable diff --git a/src/command/preview/preview-shiny.ts b/src/command/preview/preview-shiny.ts index e9d120c79f1..8c55ed1906e 100644 --- a/src/command/preview/preview-shiny.ts +++ b/src/command/preview/preview-shiny.ts @@ -33,18 +33,16 @@ import { } from "../../core/http.ts"; import { findOpenPort } from "../../core/port.ts"; import { handleHttpRequests } from "../../core/http-server.ts"; -import { kLocalhost } from "../../core/port-consts.ts"; import { normalizePath } from "../../core/path.ts"; import { previewMonitorResources } from "../../core/quarto.ts"; import { renderServices } from "../render/render-services.ts"; import { RenderFlags } from "../render/types.ts"; import { notebookContext } from "../../render/notebook/notebook-context.ts"; -import { isIpynbOutput } from "../../config/format.ts"; export interface PreviewShinyOptions extends RunOptions { pandocArgs: string[]; watchInputs: boolean; - project?: ProjectContext; + project: ProjectContext; } export async function previewShiny(options: PreviewShinyOptions) { @@ -128,8 +126,8 @@ function runPreviewControlService( return normalizePath(options.input) === normalizePath(prevReq.path) && await previewRenderRequestIsCompatible( prevReq, - options.format, options.project, + options.format, ); }; diff --git a/src/command/preview/preview.ts b/src/command/preview/preview.ts index 8617beefad4..279635c2842 100644 --- a/src/command/preview/preview.ts +++ b/src/command/preview/preview.ts @@ -85,7 +85,7 @@ import { import { isJupyterNotebook } from "../../core/jupyter/jupyter.ts"; import { watchForFileChanges } from "../../core/watch.ts"; import { previewMonitorResources } from "../../core/quarto.ts"; -import { exitWithCleanup } from "../../core/cleanup.ts"; +import { exitWithCleanup, onCleanup } from "../../core/cleanup.ts"; import { extensionFilesFromDirs, inputExtensionDirs, @@ -151,11 +151,15 @@ export async function preview( ) { const nbContext = notebookContext(); // see if this is project file - const project = await projectContext(file, nbContext); + const project = (await projectContext(file, nbContext)) || + (await singleFileProjectContext(file, nbContext)); + onCleanup(() => { + project.cleanup(); + }); // determine the target format if there isn't one in the command line args // (current we force the use of an html or pdf based format) - const format = await previewFormat(file, flags.to, undefined, project); + const format = await previewFormat(file, project, flags.to, undefined); setPreviewFormat(format, flags, pandocArgs); // render for preview (create function we can pass to watcher then call it) @@ -224,6 +228,7 @@ export async function preview( options.port!, reloader, changeHandler.render, + project, ) : project ? projectHtmlFileRequestHandler( @@ -241,6 +246,7 @@ export async function preview( result.format, reloader, changeHandler.render, + project, ); // open browser if this is a browseable format @@ -340,17 +346,17 @@ export function previewRenderRequest( export async function previewRenderRequestIsCompatible( request: PreviewRenderRequest, + project: ProjectContext, format?: string, - project?: ProjectContext, ) { if (request.version === 1) { return true; // rstudio manages its own request compatibility state } else { const reqFormat = await previewFormat( request.path, + project, request.format, undefined, - project, ); return reqFormat === format; } @@ -359,20 +365,20 @@ export async function previewRenderRequestIsCompatible( // determine the format to preview export async function previewFormat( file: string, + project: ProjectContext, format?: string, formats?: Record, - project?: ProjectContext, ) { if (format) { return format; } - const nbContext = notebookContext(); - project = project || (await singleFileProjectContext(file, nbContext)); + // const nbContext = notebookContext(); + // project = project || (await singleFileProjectContext(file, nbContext)); formats = formats || await withRenderServices( - nbContext, + project.notebookContext, (services: RenderServices) => - renderFormats(file, services, "all", project!), + renderFormats(file, services, "all", project), ); format = Object.keys(formats)[0] || "html"; return format; @@ -418,7 +424,6 @@ export async function renderForPreview( pandocArgs: string[], project?: ProjectContext, ): Promise { - // render const renderResult = await render(file, { services, @@ -426,7 +431,7 @@ export async function renderForPreview( pandocArgs: pandocArgs, previewServer: true, setProjectDir: project !== undefined, - }); + }, project); if (renderResult.error) { throw renderResult.error; } @@ -689,6 +694,7 @@ function htmlFileRequestHandler( format: Format, reloader: HttpDevServer, renderHandler: (to?: string) => Promise, + context: ProjectContext, ) { return httpFileRequestHandler( htmlFileRequestHandlerOptions( @@ -699,6 +705,7 @@ function htmlFileRequestHandler( format, reloader, renderHandler, + context, ), ); } @@ -711,7 +718,7 @@ function htmlFileRequestHandlerOptions( format: Format, devserver: HttpDevServer, renderHandler: (to?: string) => Promise, - project?: ProjectContext, + project: ProjectContext, ): HttpFileRequestOptions { // if we an alternate format on the fly we need to do a full re-render // to get the correct state back. this flag will be set whenever @@ -742,7 +749,7 @@ function htmlFileRequestHandlerOptions( prevReq && existsSync(prevReq.path) && normalizePath(prevReq.path) === normalizePath(inputFile) && - await previewRenderRequestIsCompatible(prevReq, flags.to) + await previewRenderRequestIsCompatible(prevReq, project, flags.to) ) { // don't wait for the promise so the // caller gets an immediate reply @@ -853,6 +860,7 @@ function pdfFileRequestHandler( port: number, reloader: HttpDevServer, renderHandler: () => Promise, + project: ProjectContext, ) { // start w/ the html handler (as we still need it's http reload injection) const pdfOptions = htmlFileRequestHandlerOptions( @@ -863,6 +871,7 @@ function pdfFileRequestHandler( format, reloader, renderHandler, + project, ); // pdf customizations diff --git a/src/command/render/filters.ts b/src/command/render/filters.ts index 13edc229530..863d3d9603a 100644 --- a/src/command/render/filters.ts +++ b/src/command/render/filters.ts @@ -85,7 +85,7 @@ import { quartoConfig } from "../../core/quarto.ts"; import { metadataNormalizationFilterActive } from "./normalize.ts"; import { kCodeAnnotations } from "../../format/html/format-html-shared.ts"; import { projectOutputDir } from "../../project/project-shared.ts"; -import { relative } from "../../deno_ral/path.ts"; +import { basename, relative } from "../../deno_ral/path.ts"; import { citeIndexFilterParams } from "../../project/project-cites.ts"; import { debug } from "../../deno_ral/log.ts"; import { kJatsSubarticle } from "../../format/jats/format-jats-types.ts"; @@ -660,7 +660,11 @@ async function quartoFilterParams( params[kHasResourcePath] = hasResourcePath; // The source document - params[kQuartoSource] = options.source; + if (options.project.isSingleFile) { + params[kQuartoSource] = basename(options.source); + } else { + params[kQuartoSource] = options.source; + } // profile as an array params[kQuartoProfile.toLowerCase()] = activeProfiles(); @@ -860,7 +864,7 @@ async function resolveFilterExtension( } let pathToResolve: string | null = null; - + if (typeof filter === "string") { pathToResolve = filter; } else if (typeof filter === "object" && filter.path) { @@ -869,7 +873,9 @@ async function resolveFilterExtension( if (pathToResolve) { // The filter string points to an executable file which exists - if (existsSync(pathToResolve) && !Deno.statSync(pathToResolve).isDirectory) { + if ( + existsSync(pathToResolve) && !Deno.statSync(pathToResolve).isDirectory + ) { return filter; } @@ -896,17 +902,19 @@ async function resolveFilterExtension( if (typeof filter === "string") { return extensionFilters; } else if (isFilterEntryPoint(filter)) { - return extensionFilters.map(extFilter => { + return extensionFilters.map((extFilter) => { if (typeof extFilter === "string") { return { - type: extFilter.endsWith(".lua") ? "lua" : "json" as "lua" | "json", + type: extFilter.endsWith(".lua") + ? "lua" + : "json" as "lua" | "json", path: extFilter, - at: filter.at + at: filter.at, }; } else { return { ...extFilter, - at: filter.at + at: filter.at, }; } }); diff --git a/src/command/render/render-shared.ts b/src/command/render/render-shared.ts index 63841a09383..52a4650e274 100644 --- a/src/command/render/render-shared.ts +++ b/src/command/render/render-shared.ts @@ -33,20 +33,23 @@ import { kTextPlain } from "../../core/mime.ts"; import { normalizePath } from "../../core/path.ts"; import { notebookContext } from "../../render/notebook/notebook-context.ts"; import { singleFileProjectContext } from "../../project/types/single-file/single-file.ts"; -import { assert } from "testing/asserts"; +import { ProjectContext } from "../../project/types.ts"; export async function render( path: string, options: RenderOptions, + pContext?: ProjectContext, ): Promise { // one time initialization of yaml validators setInitializer(initYamlIntelligenceResourcesFromFilesystem); await initState(); - const nbContext = notebookContext(); + const nbContext = pContext?.notebookContext || notebookContext(); // determine target context/files - let context = await projectContext(path, nbContext, options); + // let context = await projectContext(path, nbContext, options); + let context = pContext || (await projectContext(path, nbContext, options)) || + (await singleFileProjectContext(path, nbContext, options)); // Create a synthetic project when --output-dir is used without a project file // This creates a temporary .quarto directory to manage the render, which must @@ -61,6 +64,7 @@ export async function render( // set env var if requested if (context && options.setProjectDir) { + // FIXME we can't set environment variables like this with asyncs flying around Deno.env.set("QUARTO_PROJECT_DIR", context.dir); } @@ -98,10 +102,6 @@ export async function render( // validate that we didn't get any project-only options validateDocumentRenderFlags(options.flags); - assert(!context, "Expected no context here"); - // NB: singleFileProjectContext is currently not fully-featured - context = await singleFileProjectContext(path, nbContext, options); - // otherwise it's just a file render const result = await renderFiles( [{ path }], diff --git a/src/command/serve/cmd.ts b/src/command/serve/cmd.ts index ca3042fcc39..2e51022d07f 100644 --- a/src/command/serve/cmd.ts +++ b/src/command/serve/cmd.ts @@ -75,7 +75,7 @@ export const serveCommand = new Command() (services: RenderServices) => renderFormats(input, services, undefined, context), ); - const format = await previewFormat(input, undefined, formats, context); + const format = await previewFormat(input, context, undefined, formats); const result = await serve({ input, diff --git a/src/execute/jupyter/jupyter.ts b/src/execute/jupyter/jupyter.ts index a98971c7187..378515454cb 100644 --- a/src/execute/jupyter/jupyter.ts +++ b/src/execute/jupyter/jupyter.ts @@ -317,7 +317,23 @@ title: "Title" // of additional changes to our file handling code (without changes, // our output files would be called $FILE.quarto.html, which // is not what we want). So for now, we'll use .quarto_ipynb - const notebook = join(fileDir, fileStem + ".quarto_ipynb"); + let counter: number | undefined = undefined; + let notebook = join( + fileDir, + `${fileStem}.quarto_ipynb${counter ? "_" + String(counter) : ""}`, + ); + + while (existsSync(notebook)) { + if (!counter) { + counter = 1; + } else { + ++counter; + } + notebook = join( + fileDir, + `${fileStem}.quarto_ipynb${counter ? "_" + String(counter) : ""}`, + ); + } const target = { source: file, input: notebook, diff --git a/src/project/project-context.ts b/src/project/project-context.ts index a863a0d839a..8ce68776e75 100644 --- a/src/project/project-context.ts +++ b/src/project/project-context.ts @@ -106,7 +106,6 @@ import { createProjectCache } from "../core/cache/cache.ts"; import { createTempContext } from "../core/temp.ts"; import { onCleanup } from "../core/cleanup.ts"; -import { once } from "../core/once.ts"; import { Zod } from "../resources/types/zod/schema-types.ts"; import { ExternalEngine } from "../resources/types/schema-types.ts"; @@ -367,11 +366,11 @@ export async function projectContext( previewServer: renderOptions?.previewServer, diskCache: await createProjectCache(join(dir, ".quarto")), temp, - cleanup: once(() => { + cleanup: () => { cleanupFileInformationCache(result); result.diskCache.close(); temp.cleanup(); - }), + }, }; // see if the project [kProjectType] wants to filter the project config @@ -459,11 +458,11 @@ export async function projectContext( previewServer: renderOptions?.previewServer, diskCache: await createProjectCache(join(dir, ".quarto")), temp, - cleanup: once(() => { + cleanup: () => { cleanupFileInformationCache(result); result.diskCache.close(); temp.cleanup(); - }), + }, }; const { files, engines } = await projectInputFiles( result, @@ -539,11 +538,11 @@ export async function projectContext( previewServer: renderOptions?.previewServer, diskCache: await createProjectCache(join(temp.baseDir, ".quarto")), temp, - cleanup: once(() => { + cleanup: () => { cleanupFileInformationCache(context); context.diskCache.close(); temp.cleanup(); - }), + }, }; if (Deno.statSync(path).isDirectory) { const { files, engines } = await projectInputFiles(context); diff --git a/src/project/serve/serve.ts b/src/project/serve/serve.ts index 13d968ac6cd..8c4a029a5fb 100644 --- a/src/project/serve/serve.ts +++ b/src/project/serve/serve.ts @@ -810,7 +810,7 @@ function previewControlChannelRequestHandler( ); if ( prevReq && - (await previewRenderRequestIsCompatible(prevReq, flags.to, project)) + (await previewRenderRequestIsCompatible(prevReq, project, flags.to)) ) { if (isProjectInputFile(prevReq.path, project!)) { const services = renderServices(notebookContext()); diff --git a/src/project/types/single-file/single-file.ts b/src/project/types/single-file/single-file.ts index aec26a0569a..1aa24b6c308 100644 --- a/src/project/types/single-file/single-file.ts +++ b/src/project/types/single-file/single-file.ts @@ -86,10 +86,10 @@ export async function singleFileProjectContext( isSingleFile: true, diskCache: await createProjectCache(projectCacheBaseDir), temp, - cleanup: once(() => { + cleanup: () => { cleanupFileInformationCache(result); result.diskCache.close(); - }), + }, }; if (renderOptions) { result.config = { diff --git a/src/resources/filters/quarto-post/foldcode.lua b/src/resources/filters/quarto-post/foldcode.lua index ab68552c298..35a9ac50dc5 100644 --- a/src/resources/filters/quarto-post/foldcode.lua +++ b/src/resources/filters/quarto-post/foldcode.lua @@ -106,7 +106,6 @@ function fold_code_and_lift_codeblocks() }) if need_to_move_dl then assert(prev_annotated_code_block_scaffold) - print(prev_annotated_code_block_scaffold) prev_annotated_code_block_scaffold.content:insert(div) return {} end diff --git a/src/resources/filters/quarto-post/typst.lua b/src/resources/filters/quarto-post/typst.lua index 6380dc8fee7..8a55ec11c94 100644 --- a/src/resources/filters/quarto-post/typst.lua +++ b/src/resources/filters/quarto-post/typst.lua @@ -25,7 +25,6 @@ function render_typst() m["toc-indent"] = option("toc-indent") if m["number-depth"] then number_depth = tonumber(pandoc.utils.stringify(m["number-depth"])) - print(number_depth) end return m end diff --git a/src/resources/filters/quarto-pre/shiny.lua b/src/resources/filters/quarto-pre/shiny.lua index ccf2cb5e64f..28e2f7514a1 100644 --- a/src/resources/filters/quarto-pre/shiny.lua +++ b/src/resources/filters/quarto-pre/shiny.lua @@ -123,7 +123,7 @@ function server_shiny() end -- Write the code cells to a temporary file. - codeCellsOutfile = pandoc.path.split_extension(quarto.doc.output_file) .. "-cells.tmp.json" + local codeCellsOutfile = pandoc.path.split_extension(quarto.doc.output_file) .. "-cells.tmp.json" local file = io.open(codeCellsOutfile, "w") if file == nil then error("Error opening file: " .. codeCellsOutfile .. " for writing.") @@ -132,7 +132,7 @@ function server_shiny() file:close() -- Convert the json file to app.py by calling `shiny convert-cells`. - appOutfile = pandoc.path.join({ + local appOutfile = pandoc.path.join({ pandoc.path.directory(quarto.doc.output_file), "app.py" });