@@ -31,6 +31,7 @@ import {
3131import { isCancellationError , reportError } from "../utils/errorUtils" ;
3232import { normalizeFileOpening } from "../utils/fileOpeningDefaults" ;
3333import { QuickAddChoiceEngine } from "./QuickAddChoiceEngine" ;
34+ import { ChoiceAbortError } from "../errors/ChoiceAbortError" ;
3435import { MacroAbortError } from "../errors/MacroAbortError" ;
3536import { SingleTemplateEngine } from "./SingleTemplateEngine" ;
3637import { getCaptureAction , type CaptureAction } from "./captureAction" ;
@@ -130,12 +131,11 @@ export class CaptureChoiceEngine extends QuickAddChoiceEngine {
130131 getFileAndAddContentFn = ( ( path , capture , _options ) =>
131132 this . onCreateFileIfItDoesntExist ( path , capture , linkOptions )
132133 ) as typeof this . onCreateFileIfItDoesntExist ;
133- } else {
134- log . logWarning (
135- `The file ${ filePath } does not exist and "Create file if it doesn't exist" is disabled.` ,
136- ) ;
137- return ;
138- }
134+ } else {
135+ throw new ChoiceAbortError (
136+ `Target file missing: ${ filePath } . Enable "Create file if it doesn't exist" or choose an existing file.` ,
137+ ) ;
138+ }
139139
140140 const { file, newFileContent, captureContent } =
141141 await getFileAndAddContentFn ( filePath , content ) ;
@@ -258,27 +258,75 @@ export class CaptureChoiceEngine extends QuickAddChoiceEngine {
258258 }
259259
260260 const captureTo = this . choice . captureTo ;
261- const formattedCaptureTo = await this . formatFilePath ( captureTo ) ;
262-
263- // Removing the trailing slash from the capture to path because otherwise isFolder will fail
264- // to get the folder.
265- const folderPath = formattedCaptureTo . replace ( / ^ \/ $ | \/ \. m d $ | ^ \. m d $ / , "" ) ;
266- // Empty string means we suggest to capture anywhere in the vault.
267- const captureAnywhereInVault = folderPath === "" ;
268- const shouldCaptureToFolder =
269- captureAnywhereInVault || isFolder ( this . app , folderPath ) ;
270- const shouldCaptureWithTag = formattedCaptureTo . startsWith ( "#" ) ;
271-
272- if ( shouldCaptureToFolder ) {
273- return this . selectFileInFolder ( folderPath , captureAnywhereInVault ) ;
261+ const formattedCaptureTo = await this . formatter . formatFileName (
262+ captureTo ,
263+ this . choice . name ,
264+ ) ;
265+ const resolution = this . resolveCaptureTarget ( formattedCaptureTo ) ;
266+
267+ switch ( resolution . kind ) {
268+ case "vault" :
269+ return this . selectFileInFolder ( "" , true ) ;
270+ case "tag" :
271+ return this . selectFileWithTag ( resolution . tag ) ;
272+ case "folder" :
273+ return this . selectFileInFolder ( resolution . folder , false ) ;
274+ case "file" :
275+ return this . normalizeMarkdownFilePath ( "" , resolution . path ) ;
276+ }
277+ }
278+
279+ private resolveCaptureTarget (
280+ formattedCaptureTo : string ,
281+ ) :
282+ | { kind : "vault" }
283+ | { kind : "tag" ; tag : string }
284+ | { kind : "folder" ; folder : string }
285+ | { kind : "file" ; path : string } {
286+ // Resolution order:
287+ // 1) empty => vault picker
288+ // 2) #tag => tag picker
289+ // 3) trailing "/" => folder picker (explicit)
290+ // 4) ".md" => file
291+ // 5) ambiguous => folder if it exists and no same-name file exists; else file
292+ const normalizedCaptureTo = this . stripLeadingSlash (
293+ formattedCaptureTo . trim ( ) ,
294+ ) ;
295+
296+ if ( normalizedCaptureTo === "" ) {
297+ return { kind : "vault" } ;
298+ }
299+
300+ if ( normalizedCaptureTo . startsWith ( "#" ) ) {
301+ return {
302+ kind : "tag" ,
303+ tag : normalizedCaptureTo . replace ( / \. m d $ / , "" ) ,
304+ } ;
274305 }
275306
276- if ( shouldCaptureWithTag ) {
277- const tag = formattedCaptureTo . replace ( / \. m d $ / , "" ) ;
278- return this . selectFileWithTag ( tag ) ;
307+ const endsWithSlash = normalizedCaptureTo . endsWith ( "/" ) ;
308+ const folderPath = normalizedCaptureTo . replace ( / \/ + $ / , "" ) ;
309+
310+ if ( endsWithSlash ) {
311+ return { kind : "folder" , folder : folderPath } ;
312+ }
313+
314+ if ( normalizedCaptureTo . endsWith ( ".md" ) ) {
315+ return { kind : "file" , path : normalizedCaptureTo } ;
316+ }
317+
318+ // Guard against ambiguity where a folder and file share the same name.
319+ const fileCandidatePath = this . normalizeMarkdownFilePath ( "" , folderPath ) ;
320+ const fileCandidate = this . app . vault . getAbstractFileByPath (
321+ fileCandidatePath ,
322+ ) ;
323+ const fileExists = ! ! fileCandidate ;
324+
325+ if ( isFolder ( this . app , folderPath ) && ! fileExists ) {
326+ return { kind : "folder" , folder : folderPath } ;
279327 }
280328
281- return formattedCaptureTo ;
329+ return { kind : "file" , path : normalizedCaptureTo } ;
282330 }
283331
284332 private async selectFileInFolder (
0 commit comments