extension/src/language: utilize existence filter in file field

For golang/go#76331

Change-Id: I2a9f3e514646705d32d43eddcab4474eedcf0c11
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/786121
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Madeline Kalil <mkalil@google.com>
Reviewed-by: Danny Tuppeny <danny@tuppeny.com>
diff --git a/extension/src/language/form.ts b/extension/src/language/form.ts
index be85fa5..53ed92b 100644
--- a/extension/src/language/form.ts
+++ b/extension/src/language/form.ts
@@ -786,52 +786,62 @@
 	 * Helper to prompt for a single field based on its type.
 	 */
 	private async promptForField(field: FormField, prevAnswer: any | undefined): Promise<any | undefined> {
-		const type = field.type;
+		const fieldType = field.type;
 
-		switch (type.kind) {
+		switch (fieldType.kind) {
 			case 'file': {
-				// UX Decision: Explicitly separate "Open" and "Create" flows.
-				//
-				// We use this "Intent Menu" to bypass a limitation in the
-				// native OS Save Dialog.
-				//
-				// While vscode.window.showSaveDialog allows selecting both new
-				// and existing paths, it forces a system-level "Do you want to
-				// replace it?" warning if an existing file is selected.
-				//
-				// Since our server will NOT actually overwrite the file (it
-				// just needs the URI), this warning is a false alarm that
-				// confuses users. We cannot disable this warning in the OS, so
-				// we split the flow:
-				//
-				// - "Open Existing": Uses showOpenDialog (Clean UX, no warnings)
-				// - "Create New": Uses showSaveDialog (The "Overwrite" warning
-				// is unavoidable here, but users expect some friction when
-				// "creating" over an existing name, so it is acceptable).
-				const action = await vscode.window.showQuickPick(
-					[
-						{
-							label: '$(file) Open Existing File',
-							description: 'Select a file that already exists',
-							target: 'open'
-						},
-						{
-							label: '$(new-file) Create New File',
-							description: 'Select a destination for a new file',
-							target: 'save'
-						}
-					],
-					{
-						placeHolder: field.description || 'Select file action',
-						ignoreFocusOut: true
+				let actionTarget: 'open' | 'save' | undefined;
+				if (fieldType.existence !== undefined) {
+					const allowsNew = (fieldType.existence & FileExistence.New) !== 0;
+					const allowsExisting = (fieldType.existence & FileExistence.Existing) !== 0;
+					if (allowsNew && !allowsExisting) {
+						actionTarget = 'save';
+					} else if (allowsExisting && !allowsNew) {
+						actionTarget = 'open';
 					}
-				);
+				}
 
-				// TODO(hxjiang): support reading the resource kind & existence
-				// from the file kind.
+				if (actionTarget === undefined) {
+					// UX Decision: If the file existence constraint is not specified or
+					// allows both options, we explicitly separate the "Open" and "Create"
+					// flows to bypass a limitation in the native OS Save Dialog.
+					//
+					// While vscode.window.showSaveDialog allows selecting both new
+					// and existing paths, it forces a system-level "Do you want to
+					// replace it?" warning if an existing file is selected.
+					//
+					// Since our server will NOT actually overwrite the file (it
+					// just needs the URI), this warning is a false alarm that
+					// confuses users. We cannot disable this warning in the OS, so
+					// we split the flow:
+					//
+					// - "Open Existing": Uses showOpenDialog (Clean UX, no warnings)
+					// - "Create New": Uses showSaveDialog (The "Overwrite" warning
+					// is unavoidable here, but users expect some friction when
+					// "creating" over an existing name, so it is acceptable).
+					const action = await vscode.window.showQuickPick(
+						[
+							{
+								label: '$(file) Open Existing File',
+								description: 'Select a file that already exists',
+								target: 'open' as const
+							},
+							{
+								label: '$(new-file) Create New File',
+								description: 'Select a destination for a new file',
+								target: 'save' as const
+							}
+						],
+						{
+							placeHolder: field.description || 'Select file action',
+							ignoreFocusOut: true
+						}
+					);
 
-				if (!action) {
-					return undefined; // User cancelled
+					if (!action) {
+						return undefined; // User cancelled
+					}
+					actionTarget = action.target;
 				}
 
 				let defaultUri: vscode.Uri | undefined;
@@ -845,7 +855,7 @@
 					}
 				}
 
-				if (action.target === 'open') {
+				if (actionTarget === 'open') {
 					const uri = await vscode.window.showOpenDialog({
 						canSelectFiles: true,
 						canSelectFolders: true,
@@ -876,7 +886,7 @@
 				} as vscode.InputBoxOptions);
 
 			case 'enum': {
-				const pickItems = type.entries.map((entry, _) => {
+				const pickItems = fieldType.entries.map((entry, _) => {
 					return {
 						// Use description if it exists, otherwise use value
 						label: entry.description || entry.value,
@@ -895,7 +905,7 @@
 			}
 
 			case 'lazyEnum': {
-				return await this.pickLazyEnum(field.description, type.source, type.config);
+				return await this.pickLazyEnum(field.description, fieldType.source, fieldType.config);
 			}
 
 			case 'bool': {
@@ -934,7 +944,7 @@
 
 			case 'list': {
 				// Basic support for lists of primitive strings/numbers via comma-separated input
-				if (type.elementType.kind === 'string' || type.elementType.kind === 'number') {
+				if (fieldType.elementType.kind === 'string' || fieldType.elementType.kind === 'number') {
 					const rawList = await vscode.window.showInputBox({
 						prompt: `${field.description} (comma separated)`,
 						ignoreFocusOut: true
@@ -951,14 +961,14 @@
 
 					const parts = rawList.split(',').map((s) => s.trim());
 
-					if (type.elementType.kind === 'number') {
+					if (fieldType.elementType.kind === 'number') {
 						return parts.map(Number).filter((n) => !isNaN(n));
 					}
 					return parts;
 				}
 
 				vscode.window.showErrorMessage(
-					`List input for ${type.elementType.kind} is not supported in this version.`
+					`List input for ${fieldType.elementType.kind} is not supported in this version.`
 				);
 				return undefined;
 			}