Skip to content

Commit 77633b3

Browse files
cscheidcderv
authored andcommitted
Manage projectcontext objects correctly on preview
1 parent a7d8b6a commit 77633b3

10 files changed

Lines changed: 63 additions & 40 deletions

File tree

package/scripts/common/quarto

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,9 @@ QUARTO_DENO_OPTIONS="--unstable-ffi --unstable-kv --no-config --no-lock ${QUARTO
187187

188188
# --enable-experimental-regexp-engine is required for /regex/l, https://github.com/quarto-dev/quarto-cli/issues/9737
189189
if [ "$QUARTO_DENO_V8_OPTIONS" != "" ]; then
190-
QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192,${QUARTO_DENO_V8_OPTIONS}"
190+
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}"
191191
else
192-
QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192"
192+
QUARTO_DENO_V8_OPTIONS="--enable-experimental-regexp-engine,--max-old-space-size=8192,--max-heap-size=8192,--stack-trace-limit=100"
193193
fi
194194

195195
if [ "$QUARTO_DENO_EXTRA_OPTIONS" == "" ]; then

src/command/preview/cmd.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ export const previewCommand = new Command()
292292
services.cleanup();
293293
}
294294
})();
295-
const format = await previewFormat(file, flags.to, formats, project);
295+
const format = await previewFormat(file, project, flags.to, formats);
296296

297297
// see if this is server: shiny document and if it is then forward to previewShiny
298298
if (isHtmlOutput(parseFormatString(format).baseFormat)) {
@@ -314,6 +314,7 @@ export const previewCommand = new Command()
314314
format,
315315
pandocArgs: args,
316316
watchInputs: options.watchInputs!,
317+
project,
317318
});
318319
exitWithCleanup(result.code);
319320
throw new Error(); // unreachable

src/command/preview/preview-shiny.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,16 @@ import {
3333
} from "../../core/http.ts";
3434
import { findOpenPort } from "../../core/port.ts";
3535
import { handleHttpRequests } from "../../core/http-server.ts";
36-
import { kLocalhost } from "../../core/port-consts.ts";
3736
import { normalizePath } from "../../core/path.ts";
3837
import { previewMonitorResources } from "../../core/quarto.ts";
3938
import { renderServices } from "../render/render-services.ts";
4039
import { RenderFlags } from "../render/types.ts";
4140
import { notebookContext } from "../../render/notebook/notebook-context.ts";
42-
import { isIpynbOutput } from "../../config/format.ts";
4341

4442
export interface PreviewShinyOptions extends RunOptions {
4543
pandocArgs: string[];
4644
watchInputs: boolean;
47-
project?: ProjectContext;
45+
project: ProjectContext;
4846
}
4947

5048
export async function previewShiny(options: PreviewShinyOptions) {
@@ -128,8 +126,8 @@ function runPreviewControlService(
128126
return normalizePath(options.input) === normalizePath(prevReq.path) &&
129127
await previewRenderRequestIsCompatible(
130128
prevReq,
131-
options.format,
132129
options.project,
130+
options.format,
133131
);
134132
};
135133

src/command/preview/preview.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import {
8585
import { isJupyterNotebook } from "../../core/jupyter/jupyter.ts";
8686
import { watchForFileChanges } from "../../core/watch.ts";
8787
import { previewMonitorResources } from "../../core/quarto.ts";
88-
import { exitWithCleanup } from "../../core/cleanup.ts";
88+
import { exitWithCleanup, onCleanup } from "../../core/cleanup.ts";
8989
import {
9090
extensionFilesFromDirs,
9191
inputExtensionDirs,
@@ -151,11 +151,15 @@ export async function preview(
151151
) {
152152
const nbContext = notebookContext();
153153
// see if this is project file
154-
const project = await projectContext(file, nbContext);
154+
const project = (await projectContext(file, nbContext)) ||
155+
(await singleFileProjectContext(file, nbContext));
156+
onCleanup(() => {
157+
project.cleanup();
158+
});
155159

156160
// determine the target format if there isn't one in the command line args
157161
// (current we force the use of an html or pdf based format)
158-
const format = await previewFormat(file, flags.to, undefined, project);
162+
const format = await previewFormat(file, project, flags.to, undefined);
159163
setPreviewFormat(format, flags, pandocArgs);
160164

161165
// render for preview (create function we can pass to watcher then call it)
@@ -224,6 +228,7 @@ export async function preview(
224228
options.port!,
225229
reloader,
226230
changeHandler.render,
231+
project,
227232
)
228233
: project
229234
? projectHtmlFileRequestHandler(
@@ -241,6 +246,7 @@ export async function preview(
241246
result.format,
242247
reloader,
243248
changeHandler.render,
249+
project,
244250
);
245251

246252
// open browser if this is a browseable format
@@ -340,17 +346,17 @@ export function previewRenderRequest(
340346

341347
export async function previewRenderRequestIsCompatible(
342348
request: PreviewRenderRequest,
349+
project: ProjectContext,
343350
format?: string,
344-
project?: ProjectContext,
345351
) {
346352
if (request.version === 1) {
347353
return true; // rstudio manages its own request compatibility state
348354
} else {
349355
const reqFormat = await previewFormat(
350356
request.path,
357+
project,
351358
request.format,
352359
undefined,
353-
project,
354360
);
355361
return reqFormat === format;
356362
}
@@ -359,20 +365,20 @@ export async function previewRenderRequestIsCompatible(
359365
// determine the format to preview
360366
export async function previewFormat(
361367
file: string,
368+
project: ProjectContext,
362369
format?: string,
363370
formats?: Record<string, Format>,
364-
project?: ProjectContext,
365371
) {
366372
if (format) {
367373
return format;
368374
}
369-
const nbContext = notebookContext();
370-
project = project || (await singleFileProjectContext(file, nbContext));
375+
// const nbContext = notebookContext();
376+
// project = project || (await singleFileProjectContext(file, nbContext));
371377
formats = formats ||
372378
await withRenderServices(
373-
nbContext,
379+
project.notebookContext,
374380
(services: RenderServices) =>
375-
renderFormats(file, services, "all", project!),
381+
renderFormats(file, services, "all", project),
376382
);
377383
format = Object.keys(formats)[0] || "html";
378384
return format;
@@ -418,15 +424,14 @@ export async function renderForPreview(
418424
pandocArgs: string[],
419425
project?: ProjectContext,
420426
): Promise<RenderForPreviewResult> {
421-
422427
// render
423428
const renderResult = await render(file, {
424429
services,
425430
flags,
426431
pandocArgs: pandocArgs,
427432
previewServer: true,
428433
setProjectDir: project !== undefined,
429-
});
434+
}, project);
430435
if (renderResult.error) {
431436
throw renderResult.error;
432437
}
@@ -689,6 +694,7 @@ function htmlFileRequestHandler(
689694
format: Format,
690695
reloader: HttpDevServer,
691696
renderHandler: (to?: string) => Promise<RenderForPreviewResult | undefined>,
697+
context: ProjectContext,
692698
) {
693699
return httpFileRequestHandler(
694700
htmlFileRequestHandlerOptions(
@@ -699,6 +705,7 @@ function htmlFileRequestHandler(
699705
format,
700706
reloader,
701707
renderHandler,
708+
context,
702709
),
703710
);
704711
}
@@ -711,7 +718,7 @@ function htmlFileRequestHandlerOptions(
711718
format: Format,
712719
devserver: HttpDevServer,
713720
renderHandler: (to?: string) => Promise<RenderForPreviewResult | undefined>,
714-
project?: ProjectContext,
721+
project: ProjectContext,
715722
): HttpFileRequestOptions {
716723
// if we an alternate format on the fly we need to do a full re-render
717724
// to get the correct state back. this flag will be set whenever
@@ -742,7 +749,7 @@ function htmlFileRequestHandlerOptions(
742749
prevReq &&
743750
existsSync(prevReq.path) &&
744751
normalizePath(prevReq.path) === normalizePath(inputFile) &&
745-
await previewRenderRequestIsCompatible(prevReq, flags.to)
752+
await previewRenderRequestIsCompatible(prevReq, project, flags.to)
746753
) {
747754
// don't wait for the promise so the
748755
// caller gets an immediate reply
@@ -853,6 +860,7 @@ function pdfFileRequestHandler(
853860
port: number,
854861
reloader: HttpDevServer,
855862
renderHandler: () => Promise<RenderForPreviewResult | undefined>,
863+
project: ProjectContext,
856864
) {
857865
// start w/ the html handler (as we still need it's http reload injection)
858866
const pdfOptions = htmlFileRequestHandlerOptions(
@@ -863,6 +871,7 @@ function pdfFileRequestHandler(
863871
format,
864872
reloader,
865873
renderHandler,
874+
project,
866875
);
867876

868877
// pdf customizations

src/command/render/render-shared.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,23 @@ import { kTextPlain } from "../../core/mime.ts";
3333
import { normalizePath } from "../../core/path.ts";
3434
import { notebookContext } from "../../render/notebook/notebook-context.ts";
3535
import { singleFileProjectContext } from "../../project/types/single-file/single-file.ts";
36-
import { assert } from "testing/asserts";
36+
import { ProjectContext } from "../../project/types.ts";
3737

3838
export async function render(
3939
path: string,
4040
options: RenderOptions,
41+
pContext?: ProjectContext,
4142
): Promise<RenderResult> {
4243
// one time initialization of yaml validators
4344
setInitializer(initYamlIntelligenceResourcesFromFilesystem);
4445
await initState();
4546

46-
const nbContext = notebookContext();
47+
const nbContext = pContext?.notebookContext || notebookContext();
4748

4849
// determine target context/files
49-
let context = await projectContext(path, nbContext, options);
50+
// let context = await projectContext(path, nbContext, options);
51+
let context = pContext || (await projectContext(path, nbContext, options)) ||
52+
(await singleFileProjectContext(path, nbContext, options));
5053

5154
// Create a synthetic project when --output-dir is used without a project file
5255
// This creates a temporary .quarto directory to manage the render, which must
@@ -61,6 +64,7 @@ export async function render(
6164

6265
// set env var if requested
6366
if (context && options.setProjectDir) {
67+
// FIXME we can't set environment variables like this with asyncs flying around
6468
Deno.env.set("QUARTO_PROJECT_DIR", context.dir);
6569
}
6670

@@ -98,10 +102,6 @@ export async function render(
98102
// validate that we didn't get any project-only options
99103
validateDocumentRenderFlags(options.flags);
100104

101-
assert(!context, "Expected no context here");
102-
// NB: singleFileProjectContext is currently not fully-featured
103-
context = await singleFileProjectContext(path, nbContext, options);
104-
105105
// otherwise it's just a file render
106106
const result = await renderFiles(
107107
[{ path }],

src/command/serve/cmd.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const serveCommand = new Command()
7575
(services: RenderServices) =>
7676
renderFormats(input, services, undefined, context),
7777
);
78-
const format = await previewFormat(input, undefined, formats, context);
78+
const format = await previewFormat(input, context, undefined, formats);
7979

8080
const result = await serve({
8181
input,

src/execute/jupyter/jupyter.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,23 @@ title: "Title"
317317
// of additional changes to our file handling code (without changes,
318318
// our output files would be called $FILE.quarto.html, which
319319
// is not what we want). So for now, we'll use .quarto_ipynb
320-
const notebook = join(fileDir, fileStem + ".quarto_ipynb");
320+
let counter: number | undefined = undefined;
321+
let notebook = join(
322+
fileDir,
323+
`${fileStem}.quarto_ipynb${counter ? "_" + String(counter) : ""}`,
324+
);
325+
326+
while (existsSync(notebook)) {
327+
if (!counter) {
328+
counter = 1;
329+
} else {
330+
++counter;
331+
}
332+
notebook = join(
333+
fileDir,
334+
`${fileStem}.quarto_ipynb${counter ? "_" + String(counter) : ""}`,
335+
);
336+
}
321337
const target = {
322338
source: file,
323339
input: notebook,

src/project/project-context.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ import { createProjectCache } from "../core/cache/cache.ts";
107107
import { createTempContext } from "../core/temp.ts";
108108

109109
import { onCleanup } from "../core/cleanup.ts";
110-
import { once } from "../core/once.ts";
111110
import { Zod } from "../resources/types/zod/schema-types.ts";
112111
import { ExternalEngine } from "../resources/types/schema-types.ts";
113112

@@ -374,11 +373,11 @@ export async function projectContext(
374373
previewServer: renderOptions?.previewServer,
375374
diskCache: await createProjectCache(join(dir, ".quarto")),
376375
temp,
377-
cleanup: once(() => {
376+
cleanup: () => {
378377
cleanupFileInformationCache(result);
379378
result.diskCache.close();
380379
temp.cleanup();
381-
}),
380+
},
382381
};
383382

384383
// see if the project [kProjectType] wants to filter the project config
@@ -466,11 +465,11 @@ export async function projectContext(
466465
previewServer: renderOptions?.previewServer,
467466
diskCache: await createProjectCache(join(dir, ".quarto")),
468467
temp,
469-
cleanup: once(() => {
468+
cleanup: () => {
470469
cleanupFileInformationCache(result);
471470
result.diskCache.close();
472471
temp.cleanup();
473-
}),
472+
},
474473
};
475474
const { files, engines } = await projectInputFiles(
476475
result,
@@ -546,11 +545,11 @@ export async function projectContext(
546545
previewServer: renderOptions?.previewServer,
547546
diskCache: await createProjectCache(join(temp.baseDir, ".quarto")),
548547
temp,
549-
cleanup: once(() => {
548+
cleanup: () => {
550549
cleanupFileInformationCache(context);
551550
context.diskCache.close();
552551
temp.cleanup();
553-
}),
552+
},
554553
};
555554
if (Deno.statSync(path).isDirectory) {
556555
const { files, engines } = await projectInputFiles(context);

src/project/serve/serve.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ function previewControlChannelRequestHandler(
810810
);
811811
if (
812812
prevReq &&
813-
(await previewRenderRequestIsCompatible(prevReq, flags.to, project))
813+
(await previewRenderRequestIsCompatible(prevReq, project, flags.to))
814814
) {
815815
if (isProjectInputFile(prevReq.path, project!)) {
816816
const services = renderServices(notebookContext());

src/project/types/single-file/single-file.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ export async function singleFileProjectContext(
8686
isSingleFile: true,
8787
diskCache: await createProjectCache(projectCacheBaseDir),
8888
temp,
89-
cleanup: once(() => {
89+
cleanup: () => {
9090
cleanupFileInformationCache(result);
9191
result.diskCache.close();
92-
}),
92+
},
9393
};
9494
if (renderOptions) {
9595
result.config = {

0 commit comments

Comments
 (0)