Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
afcb970
fix: On a large monitor, when commands run in CodexMontior their outp…
aolin480 Mar 13, 2026
3861093
feat: Added PHPStorm IDE as an IDE option
aolin480 Mar 17, 2026
f6c9b1b
fix: Running code lines should stretch 100% of the window content
aolin480 Mar 17, 2026
41d8bb3
Fix: Commands not stretching 100% width of content area and on wide m…
aolin480 Mar 18, 2026
387f0b9
Chore: Let's not overdo it on checking if the phpstorm binary exists …
aolin480 Mar 18, 2026
c1b4a01
fix: add PHPStorm as a built-in IDE target and preserve macOS editor …
aolin480 Mar 18, 2026
1d84967
fix: use the Windows PHPStorm launcher name in open app targets
aolin480 Mar 18, 2026
3f18662
fix: use the macOS app launcher fallback for PHPStorm
aolin480 Mar 18, 2026
0732034
fix: migrate persisted open app targets to include PHPStorm
aolin480 Mar 18, 2026
026ffc9
fix: only migrate the PHPStorm open app target
aolin480 Mar 18, 2026
97969f4
fix: restore missing HashMap import in workspace IO
aolin480 Mar 18, 2026
5035fde
fix: add PHPStorm to the frontend open app defaults
aolin480 Mar 18, 2026
d372eed
fix: normalize Windows namespace paths for PHPStorm launches
aolin480 Mar 18, 2026
742857c
fix: use the Linux PHPStorm launcher in open app defaults
aolin480 Mar 18, 2026
363d2fa
fix: preserve line-aware args in the macOS PHPStorm fallback
aolin480 Mar 19, 2026
44bae44
fix: use a fresh open instance for the macOS PHPStorm fallback
aolin480 Mar 19, 2026
18b2755
fix: recognize PhpStorm app bundle names in macOS launch mapping
aolin480 Mar 19, 2026
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
80 changes: 62 additions & 18 deletions src-tauri/src/shared/workspaces_core/io.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};

Expand All @@ -7,14 +6,15 @@ use tokio::sync::Mutex;
use crate::shared::process_core::tokio_command;
#[cfg(target_os = "windows")]
use crate::shared::process_core::{build_cmd_c_command, resolve_windows_executable};
use crate::types::WorkspaceEntry;
use crate::types::{OpenAppTarget, WorkspaceEntry};
use crate::utils::normalize_windows_namespace_path;

use super::helpers::resolve_workspace_root;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LineAwareLaunchStrategy {
GotoFlag,
JetBrainsLineColumnFlags,
PathWithLineColumn,
}

Expand Down Expand Up @@ -53,6 +53,9 @@ fn command_launch_strategy(command: &str) -> Option<LineAwareLaunchStrategy> {
{
return Some(LineAwareLaunchStrategy::GotoFlag);
}
if identifier == "phpstorm" || identifier == "phpstorm64" {
return Some(LineAwareLaunchStrategy::JetBrainsLineColumnFlags);
}
if identifier == "zed" || identifier == "zed-preview" {
return Some(LineAwareLaunchStrategy::PathWithLineColumn);
}
Expand All @@ -64,6 +67,9 @@ fn app_launch_strategy(app: &str) -> Option<LineAwareLaunchStrategy> {
if normalized.contains("visual studio code") || normalized.starts_with("cursor") {
return Some(LineAwareLaunchStrategy::GotoFlag);
}
if normalized == "phpstorm" {
return Some(LineAwareLaunchStrategy::JetBrainsLineColumnFlags);
Comment thread
aolin480 marked this conversation as resolved.
Outdated
Comment thread
aolin480 marked this conversation as resolved.
Outdated
}
if normalized == "zed" || normalized.starts_with("zed ") {
return Some(LineAwareLaunchStrategy::PathWithLineColumn);
}
Expand All @@ -81,28 +87,15 @@ fn app_cli_command(app: &str) -> Option<&'static str> {
if normalized.starts_with("cursor") {
return Some("cursor");
}
if normalized == "phpstorm" {
return Some("phpstorm");
Comment on lines +91 to +92
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect the configured PhpStorm bundle on macOS

For macOS file links with a line/column, every recognized PhpStorm app name/path is collapsed to the global phpstorm launcher here, and open_workspace_in_core() then prefers that CLI (io.rs:265-273) over open -a <configured app>. Because the app name field is user-editable in Settings (src/features/settings/components/sections/SettingsOpenAppsSection.tsx:111-126), users who point it at a specific PhpStorm.app bundle (for example a non-default install or a second build) will still open whichever installation the PATH launcher targets, not the one they selected.

Useful? React with 👍 / 👎.

}
if normalized == "zed" || normalized.starts_with("zed ") {
return Some("zed");
}
None
}

fn normalize_app_identifier(app: &str) -> String {
app.trim()
.chars()
.map(|value| {
if value.is_ascii_alphanumeric() {
value.to_ascii_lowercase()
} else {
' '
}
})
.collect::<String>()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}

fn find_executable_in_path(program: &str) -> Option<PathBuf> {
let trimmed = program.trim();
if trimmed.is_empty() {
Expand All @@ -125,6 +118,22 @@ fn find_executable_in_path(program: &str) -> Option<PathBuf> {
None
}

fn normalize_app_identifier(app: &str) -> String {
app.trim()
.chars()
.map(|value| {
if value.is_ascii_alphanumeric() {
value.to_ascii_lowercase()
} else {
' '
}
})
.collect::<String>()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}

fn build_launch_args(
path: &str,
args: &[String],
Expand All @@ -141,6 +150,15 @@ fn build_launch_args(
launch_args.push("--goto".to_string());
launch_args.push(located_path);
}
Some(LineAwareLaunchStrategy::JetBrainsLineColumnFlags) => {
launch_args.push("--line".to_string());
launch_args.push(line.to_string());
if let Some(column) = column {
launch_args.push("--column".to_string());
launch_args.push(column.to_string());
}
launch_args.push(path.to_string());
Comment thread
aolin480 marked this conversation as resolved.
Outdated
}
Some(LineAwareLaunchStrategy::PathWithLineColumn) => {
let sanitized_path = normalize_windows_namespace_path(path);
let located_path = format_path_with_location(&sanitized_path, line, column);
Expand Down Expand Up @@ -323,6 +341,10 @@ mod tests {
command_launch_strategy("zed"),
Some(LineAwareLaunchStrategy::PathWithLineColumn)
);
assert_eq!(
command_launch_strategy("phpstorm64.exe"),
Some(LineAwareLaunchStrategy::JetBrainsLineColumnFlags)
);
assert_eq!(command_launch_strategy("vim"), None);
}

Expand Down Expand Up @@ -490,6 +512,28 @@ mod tests {
assert_eq!(args, vec!["/tmp/project/src/App.tsx:33".to_string()]);
}

#[test]
fn builds_line_and_column_flags_for_phpstorm_targets() {
let args = build_launch_args(
"/tmp/project/src/App.tsx",
&[],
Some(33),
Some(7),
Some(LineAwareLaunchStrategy::JetBrainsLineColumnFlags),
);

assert_eq!(
args,
vec![
"--line".to_string(),
"33".to_string(),
"--column".to_string(),
"7".to_string(),
"/tmp/project/src/App.tsx".to_string(),
]
);
}

#[test]
fn falls_back_to_plain_path_for_unknown_targets() {
let args = build_launch_args(
Expand Down
87 changes: 87 additions & 0 deletions src-tauri/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ pub(crate) fn read_settings(path: &PathBuf) -> Result<AppSettings, String> {
let data = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
let mut value: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?;
migrate_follow_up_message_behavior(&mut value);
migrate_open_app_targets(&mut value);
match serde_json::from_value(value.clone()) {
Ok(settings) => Ok(settings),
Err(_) => {
sanitize_remote_settings_for_tcp_only(&mut value);
migrate_follow_up_message_behavior(&mut value);
migrate_open_app_targets(&mut value);
serde_json::from_value(value).map_err(|e| e.to_string())
}
}
Expand Down Expand Up @@ -92,6 +94,47 @@ fn migrate_follow_up_message_behavior(value: &mut Value) {
);
}

fn migrate_open_app_targets(value: &mut Value) {
let Value::Object(root) = value else {
return;
};
let Some(Value::Array(existing_targets)) = root.get_mut("openAppTargets") else {
return;
};

let default_targets = match serde_json::to_value(AppSettings::default().open_app_targets) {
Ok(Value::Array(targets)) => targets,
_ => return,
};

let existing_ids = existing_targets
.iter()
.filter_map(|target| target.get("id").and_then(Value::as_str))
.collect::<std::collections::HashSet<_>>();

let missing_targets: Vec<Value> = default_targets
.into_iter()
.filter(|target| {
target
.get("id")
.and_then(Value::as_str)
.map(|id| !existing_ids.contains(id))
.unwrap_or(false)
})
Comment thread
aolin480 marked this conversation as resolved.
Outdated
.collect();

if missing_targets.is_empty() {
return;
}

let insert_at = existing_targets
.iter()
.position(|target| target.get("id").and_then(Value::as_str) == Some("finder"))
.unwrap_or(existing_targets.len());

existing_targets.splice(insert_at..insert_at, missing_targets);
}

#[cfg(test)]
mod tests {
use super::{read_settings, read_workspaces, write_workspaces};
Expand Down Expand Up @@ -251,4 +294,48 @@ mod tests {
let settings = read_settings(&path).expect("read settings");
assert_eq!(settings.follow_up_message_behavior, "queue");
}

#[test]
fn read_settings_migrates_missing_open_app_targets() {
let temp_dir = std::env::temp_dir().join(format!("codex-monitor-test-{}", Uuid::new_v4()));
std::fs::create_dir_all(&temp_dir).expect("create temp dir");
let path = temp_dir.join("settings.json");

std::fs::write(
&path,
r#"{
"theme": "dark",
"selectedOpenAppId": "vscode",
"openAppTargets": [
{ "id": "vscode", "label": "VS Code", "kind": "command", "appName": null, "command": "code", "args": [] },
{ "id": "cursor", "label": "Cursor", "kind": "command", "appName": null, "command": "cursor", "args": [] },
{ "id": "zed", "label": "Zed", "kind": "command", "appName": null, "command": "zed", "args": [] },
{ "id": "ghostty", "label": "Ghostty", "kind": "command", "appName": null, "command": "ghostty", "args": [] },
{ "id": "antigravity", "label": "Antigravity", "kind": "command", "appName": null, "command": "antigravity", "args": [] },
{ "id": "finder", "label": "File Manager", "kind": "finder", "appName": null, "command": null, "args": [] }
]
}"#,
)
.expect("write settings");

let settings = read_settings(&path).expect("read settings");
let ids: Vec<&str> = settings
.open_app_targets
.iter()
.map(|target| target.id.as_str())
.collect();

assert_eq!(
ids,
vec![
"vscode",
"cursor",
"zed",
"ghostty",
"antigravity",
"phpstorm",
"finder"
]
);
}
}
24 changes: 23 additions & 1 deletion src-tauri/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,12 @@ fn default_workspace_groups() -> Vec<WorkspaceGroup> {
}

fn default_open_app_targets() -> Vec<OpenAppTarget> {
let phpstorm_command = if cfg!(target_os = "windows") {
"phpstorm64.exe"
} else {
"phpstorm"
};
Comment thread
aolin480 marked this conversation as resolved.

if cfg!(target_os = "macos") {
return vec![
OpenAppTarget {
Expand Down Expand Up @@ -1043,6 +1049,14 @@ fn default_open_app_targets() -> Vec<OpenAppTarget> {
command: None,
args: Vec::new(),
},
OpenAppTarget {
id: "phpstorm".to_string(),
label: "PHPStorm".to_string(),
kind: "app".to_string(),
app_name: Some("PhpStorm".to_string()),
command: None,
args: Vec::new(),
Comment thread
aolin480 marked this conversation as resolved.
},
OpenAppTarget {
id: "finder".to_string(),
label: "Finder".to_string(),
Expand Down Expand Up @@ -1101,6 +1115,14 @@ fn default_open_app_targets() -> Vec<OpenAppTarget> {
command: Some("antigravity".to_string()),
args: Vec::new(),
},
OpenAppTarget {
id: "phpstorm".to_string(),
label: "PHPStorm".to_string(),
kind: "command".to_string(),
app_name: None,
Comment thread
aolin480 marked this conversation as resolved.
command: Some(phpstorm_command.to_string()),
args: Vec::new(),
},
OpenAppTarget {
id: "finder".to_string(),
label: file_manager_label.to_string(),
Expand Down Expand Up @@ -1364,7 +1386,7 @@ mod tests {
"vscode"
};
assert_eq!(settings.selected_open_app_id, expected_open_id);
assert_eq!(settings.open_app_targets.len(), 6);
assert_eq!(settings.open_app_targets.len(), 7);
assert_eq!(settings.open_app_targets[0].id, "vscode");
}

Expand Down
Binary file added src/assets/app-icons/phpstorm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/features/app/utils/openAppIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cursorIcon from "../../../assets/app-icons/cursor.png";
import finderIcon from "../../../assets/app-icons/finder.png";
import antigravityIcon from "../../../assets/app-icons/antigravity.png";
import ghosttyIcon from "../../../assets/app-icons/ghostty.png";
import phpstormIcon from "../../../assets/app-icons/phpstorm.png";
import vscodeIcon from "../../../assets/app-icons/vscode.png";
import zedIcon from "../../../assets/app-icons/zed.png";
import { isMacPlatform } from "../../../utils/platformPaths";
Expand Down Expand Up @@ -30,6 +31,8 @@ export function getKnownOpenAppIcon(id: string): string | null {
return zedIcon;
case "ghostty":
return ghosttyIcon;
case "phpstorm":
return phpstormIcon;
case "antigravity":
return antigravityIcon;
case "finder":
Expand Down
Loading
Loading