Skip to content

Commit f07db89

Browse files
committed
feat: improve macro open file command
1 parent d170dfd commit f07db89

9 files changed

Lines changed: 270 additions & 55 deletions
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { describe, expect, it } from "vitest";
2+
import { buildOpenFileOptions } from "./helpers/openFileOptions";
3+
import { NewTabDirection } from "../types/newTabDirection";
4+
import type { IOpenFileCommand } from "../types/macros/QuickCommands/IOpenFileCommand";
5+
import { CommandType } from "../types/macros/CommandType";
6+
7+
function createCommand(
8+
overrides: Partial<IOpenFileCommand> = {}
9+
): IOpenFileCommand {
10+
return {
11+
id: "test-id",
12+
name: "Open file: test.md",
13+
type: overrides.type ?? CommandType.OpenFile,
14+
filePath: "test.md",
15+
...overrides,
16+
};
17+
}
18+
19+
describe("buildOpenFileOptions", () => {
20+
it("respects explicit location when provided", () => {
21+
const options = buildOpenFileOptions(
22+
createCommand({ location: "right-sidebar" })
23+
);
24+
25+
expect(options.location).toBe("right-sidebar");
26+
});
27+
28+
it("reuses the current tab when openInNewTab is false", () => {
29+
const options = buildOpenFileOptions(
30+
createCommand({ openInNewTab: false })
31+
);
32+
33+
expect(options.location).toBe("reuse");
34+
expect(options.direction).toBeUndefined();
35+
});
36+
37+
it("opens a new tab without splitting when no direction is chosen", () => {
38+
const options = buildOpenFileOptions(
39+
createCommand({ openInNewTab: true, direction: undefined })
40+
);
41+
42+
expect(options.location).toBe("tab");
43+
expect(options.direction).toBeUndefined();
44+
});
45+
46+
it("splits vertically when direction is vertical", () => {
47+
const options = buildOpenFileOptions(
48+
createCommand({
49+
openInNewTab: true,
50+
direction: NewTabDirection.vertical,
51+
location: "split",
52+
})
53+
);
54+
55+
expect(options.location).toBe("split");
56+
expect(options.direction).toBe("vertical");
57+
});
58+
59+
it("splits horizontally when direction is horizontal", () => {
60+
const options = buildOpenFileOptions(
61+
createCommand({
62+
openInNewTab: true,
63+
direction: NewTabDirection.horizontal,
64+
location: "split",
65+
})
66+
);
67+
68+
expect(options.location).toBe("split");
69+
expect(options.direction).toBe("horizontal");
70+
});
71+
72+
it("opens in a new window", () => {
73+
const options = buildOpenFileOptions(
74+
createCommand({ location: "window", focus: false })
75+
);
76+
77+
expect(options.location).toBe("window");
78+
expect(options.focus).toBe(false);
79+
});
80+
});

src/engine/MacroChoiceEngine.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import type { IConditionalCommand } from "../types/macros/Conditional/ICondition
4646
import type { ScriptCondition } from "../types/macros/Conditional/types";
4747
import { evaluateCondition } from "./helpers/conditionalEvaluator";
4848
import { handleMacroAbort } from "../utils/macroAbortHandler";
49+
import { buildOpenFileOptions } from "./helpers/openFileOptions";
4950

5051
type ConditionalScriptRunner = () => Promise<unknown>;
5152

@@ -597,12 +598,9 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine {
597598
return;
598599
}
599600

600-
await openFile(this.app, file, {
601-
location: command.openInNewTab ? "split" : "tab",
602-
direction: command.direction === "horizontal" ? "horizontal" : "vertical",
603-
focus: true,
604-
mode: "default",
605-
});
601+
const openOptions = buildOpenFileOptions(command);
602+
603+
await openFile(this.app, file, openOptions);
606604
} catch (error) {
607605
log.logError(`OpenFile: Failed to open file '${command.filePath}': ${error.message}`);
608606
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { OpenFileOptions } from "../../types/fileOpening";
2+
import type { IOpenFileCommand } from "../../types/macros/QuickCommands/IOpenFileCommand";
3+
4+
export function buildOpenFileOptions(
5+
command: IOpenFileCommand
6+
): OpenFileOptions {
7+
const focus = command.focus ?? true;
8+
9+
if (command.location) {
10+
switch (command.location) {
11+
case "reuse":
12+
return { location: "reuse", focus, mode: "default" };
13+
case "tab":
14+
return { location: "tab", focus, mode: "default" };
15+
case "split": {
16+
const direction =
17+
command.direction === "horizontal" || command.direction === "vertical"
18+
? command.direction
19+
: "vertical";
20+
return {
21+
location: "split",
22+
direction,
23+
focus,
24+
mode: "default",
25+
};
26+
}
27+
case "window":
28+
return { location: "window", focus, mode: "default" };
29+
case "left-sidebar":
30+
return { location: "left-sidebar", focus, mode: "default" };
31+
case "right-sidebar":
32+
return { location: "right-sidebar", focus, mode: "default" };
33+
default:
34+
break; // fall back to legacy fields
35+
}
36+
}
37+
38+
const openInNewTab = command.openInNewTab ?? false;
39+
const legacyDirection =
40+
command.direction === "horizontal" || command.direction === "vertical"
41+
? command.direction
42+
: undefined;
43+
44+
if (!openInNewTab) {
45+
return { location: "reuse", focus, mode: "default" };
46+
}
47+
48+
if (!legacyDirection) {
49+
return { location: "tab", focus, mode: "default" };
50+
}
51+
52+
return {
53+
location: "split",
54+
direction: legacyDirection,
55+
focus,
56+
mode: "default",
57+
};
58+
}

src/gui/MacroGUIs/CommandSequenceEditor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ export class CommandSequenceEditor {
451451
private addOpenFileCommandButton(container: HTMLDivElement) {
452452
const button: ButtonComponent = new ButtonComponent(container);
453453
button
454-
.setIcon("file")
454+
.setIcon("file-search")
455455
.setTooltip("Add Open File command")
456456
.onClick(() => {
457457
this.addCommand(new OpenFileCommand());

src/gui/MacroGUIs/OpenFileCommandSettingsModal.ts

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { App } from "obsidian";
22
import { Modal, Setting, ButtonComponent } from "obsidian";
33
import type { IOpenFileCommand } from "../../types/macros/QuickCommands/IOpenFileCommand";
44
import { NewTabDirection } from "../../types/newTabDirection";
5+
import type { OpenLocation } from "../../types/fileOpening";
6+
import { CommandType } from "../../types/macros/CommandType";
57

68
export class OpenFileCommandSettingsModal extends Modal {
79
public waitForClose: Promise<IOpenFileCommand | null>;
@@ -13,7 +15,11 @@ export class OpenFileCommandSettingsModal extends Modal {
1315
constructor(app: App, command: IOpenFileCommand) {
1416
super(app);
1517
this.originalCommand = command;
16-
this.command = { ...command }; // Create a copy to avoid mutating original
18+
this.command = { ...command, type: CommandType.OpenFile }; // copy and ensure type
19+
20+
// Backfill defaults for legacy commands
21+
this.command.focus = this.command.focus ?? true;
22+
this.command.location = this.command.location ?? this.deriveLocation();
1723

1824
this.waitForClose = new Promise<IOpenFileCommand | null>((resolve) => {
1925
this.resolvePromise = resolve;
@@ -47,12 +53,9 @@ export class OpenFileCommandSettingsModal extends Modal {
4753
headerEl.style.textAlign = "center";
4854

4955
this.addFilePathSetting();
50-
this.addOpenInNewTabSetting();
51-
52-
if (this.command.openInNewTab) {
53-
this.addDirectionSetting();
54-
}
55-
56+
this.addOpenLocationSetting();
57+
this.addFocusSetting();
58+
5659
this.addButtonBar();
5760
}
5861

@@ -72,56 +75,80 @@ export class OpenFileCommandSettingsModal extends Modal {
7275

7376

7477

75-
private addOpenInNewTabSetting() {
78+
private addOpenLocationSetting() {
7679
new Setting(this.contentEl)
77-
.setName("Open in new tab")
78-
.setDesc("Open the file in a new tab")
79-
.addToggle(toggle => toggle
80-
.setValue(this.command.openInNewTab || false)
81-
.onChange(value => {
82-
this.command.openInNewTab = value;
83-
// Clear direction when new tab is disabled
84-
if (!value) {
85-
this.command.direction = undefined;
86-
}
87-
this.reload();
88-
})
89-
);
80+
.setName("Where to open")
81+
.setDesc("Choose tab, split, window, or sidebar")
82+
.addDropdown((dropdown) => {
83+
dropdown
84+
.addOption("reuse", "Reuse active tab")
85+
.addOption("tab", "New tab")
86+
.addOption("split", "Split")
87+
.addOption("window", "New window")
88+
.addOption("left-sidebar", "Left sidebar")
89+
.addOption("right-sidebar", "Right sidebar")
90+
.setValue((this.command.location ?? this.deriveLocation()).toString())
91+
.onChange((value) => {
92+
this.command.location = value as OpenLocation;
93+
if (value !== "split") {
94+
this.command.direction = undefined;
95+
} else if (!this.command.direction) {
96+
this.command.direction = NewTabDirection.vertical;
97+
}
98+
this.reload();
99+
});
100+
});
101+
102+
if ((this.command.location ?? this.deriveLocation()) === "split") {
103+
this.addDirectionSetting();
104+
}
90105
}
91106

92107
private addDirectionSetting() {
93108
new Setting(this.contentEl)
94109
.setName("Split direction")
95-
.setDesc("Which direction to split when opening in new tab")
96-
.addDropdown(dropdown => {
110+
.setDesc("Which direction to split when opening")
111+
.addDropdown((dropdown) => {
97112
dropdown
98-
.addOption("", "No split")
99-
.addOption(NewTabDirection.horizontal, "Horizontal")
100113
.addOption(NewTabDirection.vertical, "Vertical")
101-
.setValue(this.command.direction || "")
102-
.onChange(value => {
103-
this.command.direction = value as NewTabDirection || undefined;
114+
.addOption(NewTabDirection.horizontal, "Horizontal")
115+
.setValue(this.command.direction ?? NewTabDirection.vertical)
116+
.onChange((value) => {
117+
this.command.direction = value as NewTabDirection;
104118
});
105119
});
106120
}
107121

122+
private addFocusSetting() {
123+
new Setting(this.contentEl)
124+
.setName("Focus opened tab")
125+
.setDesc("Bring the opened file to the foreground")
126+
.addToggle((toggle) =>
127+
toggle
128+
.setValue(this.command.focus ?? true)
129+
.onChange((value) => {
130+
this.command.focus = value;
131+
})
132+
);
133+
}
134+
135+
private deriveLocation(): OpenLocation {
136+
if (this.command.location) return this.command.location;
137+
if (this.command.openInNewTab) {
138+
return this.command.direction ? "split" : "tab";
139+
}
140+
return "reuse";
141+
}
142+
108143

109144

110145
private addButtonBar() {
111146
const buttonContainer = this.contentEl.createDiv();
112147
buttonContainer.style.display = "flex";
113-
buttonContainer.style.justifyContent = "space-between";
148+
buttonContainer.style.justifyContent = "flex-end";
149+
buttonContainer.style.gap = "8px";
114150
buttonContainer.style.marginTop = "20px";
115151

116-
const saveButton = new ButtonComponent(buttonContainer);
117-
saveButton
118-
.setButtonText("Save")
119-
.setCta()
120-
.onClick(() => {
121-
this.resolveWithGuard(this.command);
122-
this.close();
123-
});
124-
125152
const cancelButton = new ButtonComponent(buttonContainer);
126153
cancelButton
127154
.setButtonText("Cancel")
@@ -130,6 +157,15 @@ export class OpenFileCommandSettingsModal extends Modal {
130157
this.resolveWithGuard(null);
131158
this.close();
132159
});
160+
161+
const saveButton = new ButtonComponent(buttonContainer);
162+
saveButton
163+
.setButtonText("Save")
164+
.setCta()
165+
.onClick(() => {
166+
this.resolveWithGuard(this.command);
167+
this.close();
168+
});
133169
}
134170

135171
private reload() {

src/types/macros/QuickCommands/IOpenFileCommand.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ICommand } from "../ICommand";
22
import type { NewTabDirection } from "../../newTabDirection";
3+
import type { OpenLocation } from "../../fileOpening";
34

45
export interface IOpenFileCommand extends ICommand {
56
/** File path with optional formatting like "{{DATE}}todo.md" */
@@ -8,4 +9,6 @@ export interface IOpenFileCommand extends ICommand {
89
/** Open file options */
910
openInNewTab?: boolean;
1011
direction?: NewTabDirection;
12+
location?: OpenLocation;
13+
focus?: boolean;
1114
}

0 commit comments

Comments
 (0)