fix(#74): PowerShell detection + per-OS shell paths

- Expand Windows PowerShell detection to include MS Store install path
  (%LOCALAPPDATA%\Microsoft\WindowsApps\pwsh.exe) in addition to standard
  installer path (%ProgramFiles%\PowerShell\7\pwsh.exe)

- Add -ExecutionPolicy Bypass to PowerShell shell integration script invocation
  to bypass default Restricted policy without affecting system-wide settings

- Add per-OS shell path settings (Windows/macOS/Linux) to allow cross-platform
  users to configure shell paths separately without reconfiguring when switching
  devices. Legacy shellPath field kept as fallback for existing users.

- Add resolveShellPath() helper to pick platform-specific shell path at spawn time
  with graceful fallback to legacy shellPath value for backwards compatibility

Fixes #74
Fixes cross-platform shell configuration issues reported in community feedback

Co-Authored-By: Claude
This commit is contained in:
LeanProductivity 2026-05-26 12:46:16 +03:00
parent db688ee666
commit a3cd16ea45
4 changed files with 54 additions and 10 deletions

View file

@ -39,10 +39,15 @@ function loadNodePty(pluginDir: string): NodePtyModule {
function getDefaultShell(): string {
if (Platform.isWin) {
const pwsh = process.env.ProgramFiles + "\\PowerShell\\7\\pwsh.exe";
const pwshPaths = [
process.env.ProgramFiles + "\\PowerShell\\7\\pwsh.exe", // standard installer
(process.env.LOCALAPPDATA || "") + "\\Microsoft\\WindowsApps\\pwsh.exe", // MS Store
];
try {
const fs = window.require("fs") as typeof import("fs");
if (fs.existsSync(pwsh)) return pwsh;
for (const p of pwshPaths) {
if (p && fs.existsSync(p)) return p;
}
} catch {
// ignore
}

View file

@ -1,4 +1,4 @@
import { App, ColorComponent, DropdownComponent, Notice, PluginSettingTab, Setting, setIcon } from "obsidian";
import { App, ColorComponent, DropdownComponent, Notice, Platform, PluginSettingTab, Setting, setIcon } from "obsidian";
import type TerminalPlugin from "./main";
import type { RecentSession, SavedViewState } from "./session-state";
import {
@ -22,7 +22,10 @@ export type WikiLinkInsertMode = "wikilink" | "vault-path" | "absolute-path";
export type CursorStyle = "block" | "bar" | "underline";
export interface TerminalPluginSettings {
shellPath: string;
shellPath: string; // legacy — kept for migration, not surfaced in UI
shellPathWin: string;
shellPathMac: string;
shellPathLinux: string;
startupCommand: string;
fontSize: number;
fontFamily: string;
@ -58,6 +61,9 @@ export interface TerminalPluginSettings {
export const DEFAULT_SETTINGS: TerminalPluginSettings = {
shellPath: "",
shellPathWin: "",
shellPathMac: "",
shellPathLinux: "",
startupCommand: "",
fontSize: 14,
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
@ -88,6 +94,12 @@ export const DEFAULT_SETTINGS: TerminalPluginSettings = {
wikiLinkInsertMode: "wikilink",
};
export function resolveShellPath(settings: TerminalPluginSettings): string {
if (Platform.isWin) return settings.shellPathWin || settings.shellPath;
if (Platform.isMacOS) return settings.shellPathMac || settings.shellPath;
return settings.shellPathLinux || settings.shellPath;
}
export class TerminalSettingTab extends PluginSettingTab {
plugin: TerminalPlugin;
private pendingNewColorName = "";
@ -316,14 +328,40 @@ export class TerminalSettingTab extends PluginSettingTab {
new Setting(containerEl).setName("Behavior").setHeading();
new Setting(containerEl)
.setName("Shell path")
.setDesc("Leave empty to auto-detect your default shell")
.setName("Shell path (Windows)")
.setDesc("Leave empty to auto-detect. Separate paths let you switch devices without reconfiguring.")
.addText((text) =>
text
.setPlaceholder("Auto-detect")
.setValue(this.plugin.settings.shellPath)
.setValue(this.plugin.settings.shellPathWin)
.onChange(async (value) => {
this.plugin.settings.shellPath = value;
this.plugin.settings.shellPathWin = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Shell path (macOS)")
.setDesc("Leave empty to auto-detect. Separate paths let you switch devices without reconfiguring.")
.addText((text) =>
text
.setPlaceholder("Auto-detect")
.setValue(this.plugin.settings.shellPathMac)
.onChange(async (value) => {
this.plugin.settings.shellPathMac = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Shell path (Linux)")
.setDesc("Leave empty to auto-detect. Separate paths let you switch devices without reconfiguring.")
.addText((text) =>
text
.setPlaceholder("Auto-detect")
.setValue(this.plugin.settings.shellPathLinux)
.onChange(async (value) => {
this.plugin.settings.shellPathLinux = value;
await this.plugin.saveSettings();
})
);

View file

@ -104,7 +104,7 @@ export function getShellIntegration(
const initPath = ensureScript(scriptDir, "pwsh-init.ps1", PWSH_SCRIPT);
return {
env: {},
args: ["-NoLogo", "-NoExit", "-File", initPath],
args: ["-NoLogo", "-NoExit", "-ExecutionPolicy", "Bypass", "-File", initPath],
};
}
// cmd.exe — no hook support

View file

@ -12,6 +12,7 @@ import { mixHex } from "./color-utils";
import { findTabColor, DEFAULT_TINT_STRENGTH, MAX_TINT_STRENGTH } from "./tab-colors";
import { ThemeRegistry } from "./theme-registry";
import type { TerminalPluginSettings, NotificationSound } from "./settings";
import { resolveShellPath } from "./settings";
import type { BinaryManager } from "./binary-manager";
import type { SavedTab } from "./session-state";
import { WikiLinkAutocomplete, type AutocompleteEntry } from "./wikilink-autocomplete";
@ -724,7 +725,7 @@ export class TerminalTabManager {
}
try {
pty.spawn(this.settings.shellPath, sessionCwd, cols, rows);
pty.spawn(resolveShellPath(this.settings), sessionCwd, cols, rows);
} catch (err) {
const message = err instanceof Error ? err.message : "unknown error";
console.error("Terminal: failed to spawn shell", err);