Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/engine/MacroChoiceEngine.openFileOptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, expect, it } from "vitest";
import { buildOpenFileOptions } from "./helpers/openFileOptions";
import { NewTabDirection } from "../types/newTabDirection";
import type { IOpenFileCommand } from "../types/macros/QuickCommands/IOpenFileCommand";
import { CommandType } from "../types/macros/CommandType";

function createCommand(
overrides: Partial<IOpenFileCommand> = {}
): IOpenFileCommand {
return {
id: "test-id",
name: "Open file: test.md",
type: overrides.type ?? CommandType.OpenFile,
filePath: "test.md",
...overrides,
};
}

describe("buildOpenFileOptions", () => {
it("respects explicit location when provided", () => {
const options = buildOpenFileOptions(
createCommand({ location: "right-sidebar" })
);

expect(options.location).toBe("right-sidebar");
});

it("keeps legacy default behavior: new tab when openInNewTab is false", () => {
const options = buildOpenFileOptions(
createCommand({ openInNewTab: false, location: undefined })
);

expect(options.location).toBe("tab");
expect(options.direction).toBeUndefined();
});

it("keeps legacy default behavior: split when openInNewTab is true and no direction", () => {
const options = buildOpenFileOptions(
createCommand({ openInNewTab: true, direction: undefined, location: undefined })
);

expect(options.location).toBe("split");
expect(options.direction).toBe("vertical");
});

it("reuses the current tab when openInNewTab is false and location set to reuse", () => {
const options = buildOpenFileOptions(
createCommand({ openInNewTab: false, location: "reuse" })
);

expect(options.location).toBe("reuse");
expect(options.direction).toBeUndefined();
});

it("opens a new tab without splitting when location is explicitly tab", () => {
const options = buildOpenFileOptions(
createCommand({ openInNewTab: true, direction: undefined, location: "tab" })
);

expect(options.location).toBe("tab");
expect(options.direction).toBeUndefined();
});

it("splits vertically when direction is vertical", () => {
const options = buildOpenFileOptions(
createCommand({
openInNewTab: true,
direction: NewTabDirection.vertical,
location: "split",
})
);

expect(options.location).toBe("split");
expect(options.direction).toBe("vertical");
});

it("splits horizontally when direction is horizontal", () => {
const options = buildOpenFileOptions(
createCommand({
openInNewTab: true,
direction: NewTabDirection.horizontal,
location: "split",
})
);

expect(options.location).toBe("split");
expect(options.direction).toBe("horizontal");
});

it("opens in a new window", () => {
const options = buildOpenFileOptions(
createCommand({ location: "window", focus: false })
);

expect(options.location).toBe("window");
expect(options.focus).toBe(false);
});
});
10 changes: 4 additions & 6 deletions src/engine/MacroChoiceEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import type { IConditionalCommand } from "../types/macros/Conditional/ICondition
import type { ScriptCondition } from "../types/macros/Conditional/types";
import { evaluateCondition } from "./helpers/conditionalEvaluator";
import { handleMacroAbort } from "../utils/macroAbortHandler";
import { buildOpenFileOptions } from "./helpers/openFileOptions";

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

Expand Down Expand Up @@ -597,12 +598,9 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine {
return;
}

await openFile(this.app, file, {
location: command.openInNewTab ? "split" : "tab",
direction: command.direction === "horizontal" ? "horizontal" : "vertical",
focus: true,
mode: "default",
});
const openOptions = buildOpenFileOptions(command);

await openFile(this.app, file, openOptions);
} catch (error) {
log.logError(`OpenFile: Failed to open file '${command.filePath}': ${error.message}`);
}
Expand Down
57 changes: 57 additions & 0 deletions src/engine/helpers/openFileOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { OpenFileOptions } from "../../types/fileOpening";
import type { IOpenFileCommand } from "../../types/macros/QuickCommands/IOpenFileCommand";

export function buildOpenFileOptions(
command: IOpenFileCommand
): OpenFileOptions {
const focus = command.focus ?? true;

if (command.location) {
switch (command.location) {
case "reuse":
return { location: "reuse", focus, mode: "default" };
case "tab":
return { location: "tab", focus, mode: "default" };
case "split": {
const direction =
command.direction === "horizontal" || command.direction === "vertical"
? command.direction
: "vertical";
return {
location: "split",
direction,
focus,
mode: "default",
};
}
case "window":
return { location: "window", focus, mode: "default" };
case "left-sidebar":
return { location: "left-sidebar", focus, mode: "default" };
case "right-sidebar":
return { location: "right-sidebar", focus, mode: "default" };
default:
break; // fall back to legacy fields
}
}

const openInNewTab = command.openInNewTab ?? false;
const legacyDirection =
command.direction === "horizontal" || command.direction === "vertical"
? command.direction
: undefined;

// Legacy mapping (pre-location field):
// openInNewTab === false -> open in a new tab (not reuse)
// openInNewTab === true without direction -> split (default vertical)
if (!openInNewTab) {
return { location: "tab", focus, mode: "default" };
}

return {
location: "split",
direction: legacyDirection ?? "vertical",
focus,
mode: "default",
};
}
2 changes: 1 addition & 1 deletion src/gui/MacroGUIs/CommandSequenceEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export class CommandSequenceEditor {
private addOpenFileCommandButton(container: HTMLDivElement) {
const button: ButtonComponent = new ButtonComponent(container);
button
.setIcon("file")
.setIcon("file-search")
.setTooltip("Add Open File command")
.onClick(() => {
this.addCommand(new OpenFileCommand());
Expand Down
117 changes: 79 additions & 38 deletions src/gui/MacroGUIs/OpenFileCommandSettingsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { App } from "obsidian";
import { Modal, Setting, ButtonComponent } from "obsidian";
import type { IOpenFileCommand } from "../../types/macros/QuickCommands/IOpenFileCommand";
import { NewTabDirection } from "../../types/newTabDirection";
import type { OpenLocation } from "../../types/fileOpening";
import { CommandType } from "../../types/macros/CommandType";

export class OpenFileCommandSettingsModal extends Modal {
public waitForClose: Promise<IOpenFileCommand | null>;
Expand All @@ -13,7 +15,11 @@ export class OpenFileCommandSettingsModal extends Modal {
constructor(app: App, command: IOpenFileCommand) {
super(app);
this.originalCommand = command;
this.command = { ...command }; // Create a copy to avoid mutating original
this.command = { ...command, type: CommandType.OpenFile }; // copy and ensure type

// Backfill defaults for legacy commands
this.command.focus = this.command.focus ?? true;
this.command.location = this.command.location ?? this.deriveLocation();

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

this.addFilePathSetting();
this.addOpenInNewTabSetting();

if (this.command.openInNewTab) {
this.addDirectionSetting();
}

this.addOpenLocationSetting();
this.addFocusSetting();

this.addButtonBar();
}

Expand All @@ -72,56 +75,85 @@ export class OpenFileCommandSettingsModal extends Modal {



private addOpenInNewTabSetting() {
private addOpenLocationSetting() {
const locationOptions: { value: OpenLocation; label: string }[] = [
{ value: "reuse", label: "Reuse active tab" },
{ value: "tab", label: "New tab" },
{ value: "split", label: "Split" },
{ value: "window", label: "New window" },
{ value: "left-sidebar", label: "Left sidebar" },
{ value: "right-sidebar", label: "Right sidebar" },
];

new Setting(this.contentEl)
.setName("Open in new tab")
.setDesc("Open the file in a new tab")
.addToggle(toggle => toggle
.setValue(this.command.openInNewTab || false)
.onChange(value => {
this.command.openInNewTab = value;
// Clear direction when new tab is disabled
if (!value) {
this.command.direction = undefined;
}
this.reload();
})
);
.setName("Where to open")
.setDesc("Choose tab, split, window, or sidebar")
.addDropdown((dropdown) => {
for (const { value, label } of locationOptions) {
dropdown.addOption(value, label);
}

dropdown
.setValue(this.command.location ?? this.deriveLocation())
.onChange((value: OpenLocation) => {
this.command.location = value;
if (value === "split" && !this.command.direction) {
this.command.direction = NewTabDirection.vertical;
}
this.reload();
});
});

if ((this.command.location ?? this.deriveLocation()) === "split") {
this.addDirectionSetting();
}
}

private addDirectionSetting() {
new Setting(this.contentEl)
.setName("Split direction")
.setDesc("Which direction to split when opening in new tab")
.addDropdown(dropdown => {
.setDesc("Which direction to split when opening")
.addDropdown((dropdown) => {
dropdown
.addOption("", "No split")
.addOption(NewTabDirection.horizontal, "Horizontal")
.addOption(NewTabDirection.vertical, "Vertical")
.setValue(this.command.direction || "")
.onChange(value => {
this.command.direction = value as NewTabDirection || undefined;
.addOption(NewTabDirection.horizontal, "Horizontal")
.setValue(this.command.direction ?? NewTabDirection.vertical)
.onChange((value) => {
this.command.direction = value as NewTabDirection;
});
});
}

private addFocusSetting() {
new Setting(this.contentEl)
.setName("Focus opened file")
.setDesc("Bring the opened file to the foreground")
.addToggle((toggle) =>
toggle
.setValue(this.command.focus ?? true)
.onChange((value) => {
this.command.focus = value;
})
);
}

private deriveLocation(): OpenLocation {
if (this.command.location) return this.command.location;
if (this.command.openInNewTab) {
return "split";
}
return "tab";
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.



private addButtonBar() {
const buttonContainer = this.contentEl.createDiv();
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "space-between";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "8px";
buttonContainer.style.marginTop = "20px";

const saveButton = new ButtonComponent(buttonContainer);
saveButton
.setButtonText("Save")
.setCta()
.onClick(() => {
this.resolveWithGuard(this.command);
this.close();
});

const cancelButton = new ButtonComponent(buttonContainer);
cancelButton
.setButtonText("Cancel")
Expand All @@ -130,6 +162,15 @@ export class OpenFileCommandSettingsModal extends Modal {
this.resolveWithGuard(null);
this.close();
});

const saveButton = new ButtonComponent(buttonContainer);
saveButton
.setButtonText("Save")
.setCta()
.onClick(() => {
this.resolveWithGuard(this.command);
this.close();
});
}

private reload() {
Expand Down
3 changes: 3 additions & 0 deletions src/types/macros/QuickCommands/IOpenFileCommand.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ICommand } from "../ICommand";
import type { NewTabDirection } from "../../newTabDirection";
import type { OpenLocation } from "../../fileOpening";

export interface IOpenFileCommand extends ICommand {
/** File path with optional formatting like "{{DATE}}todo.md" */
Expand All @@ -8,4 +9,6 @@ export interface IOpenFileCommand extends ICommand {
/** Open file options */
openInNewTab?: boolean;
direction?: NewTabDirection;
location?: OpenLocation;
focus?: boolean;
}
Loading