Skip to content

Commit

Permalink
Support wezterm
Browse files Browse the repository at this point in the history
  • Loading branch information
dandavison committed Sep 2, 2024
1 parent 7a53d9b commit 76166c3
Show file tree
Hide file tree
Showing 17 changed files with 357 additions and 184 deletions.
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ lazy_static = "1.4.0"
regex = "1.9.1"
tokio = { version = "1", features = ["full"] }
url = "2.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
serve:
cargo build --release
serve: build
./target/release/wormhole

serve-tmux: build
TMUX=$$TMUX ./target/release/wormhole

build:
cargo build --release
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
[_Personal project under development, currently implemented only for a MacOS: VSCode/PyCharm + Tmux development environment_]
_This is a personal project under development, implemented only for MacOS (e.g. it uses hammerspoon in places, and the `open` command). It should work in a VSCode + Wezterm development environment, since that's what I use. It was written initially for VSCode + Alacritty/Tmux, and it could be made to work with Pycharm/IntelliJ with minor modifications._

Wormhole is for people who work on multiple projects/repositories concurrently.
Wormhole is for people who:
- work on multiple projects/repositories concurrently
- use an editor/IDE that does not run in the terminal
- use a terminal that is not the integrated terminal of their IDE

When you switch to work on a different project, two things should happen:

1. Your editor/IDE should switch to the new project workspace.
2. Your terminal emulator should switch to the new project directory / tmux window.
2. Your terminal emulator should switch to a window/tab/workspace with shell processes using the new project directory.

Wormhole makes that be true.
It is an HTTP service providing the following commands:
Expand All @@ -16,7 +19,7 @@ It is an HTTP service providing the following commands:

## Installation

Wormhole is currently implemented for MacOS only since it uses hammerspoon to work with your window manager. It binds to port 7117 by default.
Wormhole binds to port 7117 by default.

1. Clone this repo
2. Check that the editor and other settings in `src/config.rs` are appropriate for your environment
Expand Down
2 changes: 2 additions & 0 deletions cli/wormhole-next
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
curl -s http://wormhole:7117/next-project/
2 changes: 2 additions & 0 deletions cli/wormhole-previous
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
curl -s http://wormhole:7117/previous-project/
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::editor::Editor;
use crate::terminal::Terminal;

pub const EDITOR: Editor = Editor::VSCode;
pub const TERMINAL: Terminal = Terminal::Alacritty { tmux: true };
pub const TERMINAL: Terminal = Terminal::Wezterm;

pub const PROJECTS_FILE: &'static str = "~/.config/wormhole/wormhole-projects.txt";

Expand Down
102 changes: 35 additions & 67 deletions src/editor.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
use std::{path::Path, process::Command};
use crate::{config, project_path::ProjectPath, util::execute_command, wormhole::WindowAction};

use crate::{
config, hammerspoon,
project::Project,
project_path::ProjectPath,
tmux,
util::{error, info},
wormhole::WindowAction,
};
use crate::ps;

#[allow(dead_code)]
#[derive(Debug)]
Expand All @@ -16,80 +9,55 @@ pub enum Editor {
VSCode,
VSCodeInsiders,
PyCharm,
PyCharmCE,
}
use Editor::*;

impl Editor {
fn open_file_uri(&self, absolute_path: &Path, line: Option<usize>) -> String {
let path = absolute_path.to_str().unwrap();
let line = line.unwrap_or(1);
match self {
IntelliJ => format!("idea://open?file={path}&line={line}"),
PyCharm => format!("pycharm://open?file={path}&line={line}"),
VSCode => format!("vscode://file/{path}:{line}"),
VSCodeInsiders => format!("vscode-insiders://file/{path}:{line}"),
}
}

pub fn application_name(&self) -> &'static str {
match self {
VSCodeInsiders => "Code - Insiders",
VSCode => "Code",
PyCharm => "PyCharm",
PyCharmCE => "PyCharm",
IntelliJ => "IntelliJ",
}
}
}

fn open_project(project: &Project) -> Result<(), String> {
let executable = match config::EDITOR {
VSCode | VSCodeInsiders => "code",
PyCharm => "pycharm",
IntelliJ => "idea",
};
tmux::tmux(
[
"send-keys",
"-t",
&project.name,
&format!("{executable} ."),
"Enter",
]
.iter(),
);
Ok(())
}

fn select_project(project: &Project, window_action: &WindowAction) -> bool {
hammerspoon::select_editor_workspace(config::EDITOR, project, window_action)
pub fn macos_application_bundle_name(&self) -> &'static str {
match self {
VSCodeInsiders => "Visual Studio Code - Insiders",
VSCode => "Visual Studio Code",
PyCharm => "PyCharm",
PyCharmCE => "PyCharm CE",
IntelliJ => "IntelliJ IDEA",
}
}
}

pub fn open_path(path: &ProjectPath, window_action: WindowAction) -> Result<(), String> {
info(&format!("editor::open_path({path:?}, {window_action:?})"));
if !select_project(&path.project, &window_action) {
info("Failed to select project; trying to open workspace");
open_project(&path.project)?;
if !select_project(&path.project, &window_action) {
error("Failed to find editor workspace after opening editor in project directory")
ps!("editor::open_path({path:?}, {window_action:?})");
let project_path = path.absolute_path();
match window_action {
WindowAction::Raise => {
execute_command(
config::EDITOR.application_name(),
// HACK: VSCode-specific
["-g", project_path.to_str().unwrap()],
&path.project.path,
);
}
WindowAction::Focus => {
execute_command(
"open",
[
"-g",
"-a",
config::EDITOR.macos_application_bundle_name(),
project_path.to_str().unwrap(),
],
&path.project.path,
);
}
}
if path.relative_path.is_some() {
open_editor_application_at_path(path)?
}
Ok(())
}

fn open_editor_application_at_path(path: &ProjectPath) -> Result<(), String> {
let line = path
.relative_path
.as_ref()
.and_then(|(_, line)| line.to_owned());
let uri = config::EDITOR.open_file_uri(&path.absolute_path(), line);

info(&format!("open_editor_application_at_path({uri})"));
if let Err(err) = Command::new("open").arg(&uri).spawn() {
Err(format!("Failed to open URI: {}: {}", uri, err))
} else {
Ok(())
}
}
91 changes: 15 additions & 76 deletions src/hammerspoon.rs
Original file line number Diff line number Diff line change
@@ -1,96 +1,35 @@
use std::process::Command;
use std::str;

use crate::editor::Editor;
use crate::project::Project;
use crate::util::{error, info, panic, warn};
use crate::wormhole::{Application, WindowAction};

impl WindowAction {
fn lua(&self) -> &'static str {
match self {
WindowAction::Focus => "focus",
WindowAction::Raise => "raise",
}
}
}
use crate::config;
use crate::util::{error, panic, warn};
use crate::wormhole::Application;

pub fn current_application() -> Application {
let mut app = Application::Other;
match str::from_utf8(&hammerspoon(
r#"
local focusedWindow = hs.window.focusedWindow()
if focusedWindow then
print(focusedWindow:application():title())
end
"#,
)) {
))
.map(str::trim)
{
Ok(app_title) => {
let app_title = app_title.trim();
if app_title == "Alacritty" {
app = Application::Terminal;
if app_title == config::TERMINAL.application_name() {
Application::Terminal
} else if app_title == config::EDITOR.application_name() {
Application::Editor
} else {
for editor in [
Editor::VSCodeInsiders,
Editor::VSCode,
Editor::PyCharm,
Editor::IntelliJ,
] {
if app_title == editor.application_name() {
app = Application::Editor;
}
}
Application::Other
}
}
Err(err) => warn(&format!("current_application() ERROR: {err}")),
}
app
}

pub fn select_editor_workspace(editor: Editor, project: &Project, action: &WindowAction) -> bool {
info(&format!(
"hammerspoon::select_editor_workspace({editor:?}, {project:?} {action:?})"
));
let success_marker = "Found matching window";
let stdout = hammerspoon(&format!(
r#"
print('Searching for application "{}" window matching "{}"')
function string:endswith(s)
return self:sub(-#s) == s
end
local function is_requested_workspace(window)
if window:application():title() == '{}' then
print('window:title() = ' .. window:title())
return window:title():endswith('{}') or window:title():endswith('{} (Workspace)')
end
end
for _, window in pairs(hs.window.allWindows()) do
if is_requested_workspace(window) then
print('Found matching application: ' .. window:application():title())
print('{}: ' .. window:title())
window:{}()
break
end
end
"#,
editor.application_name(),
project.name,
editor.application_name(),
project.name,
project.name,
success_marker,
action.lua(),
));

let mut success = false;
for line in str::from_utf8(&stdout).unwrap().split_terminator("\n") {
info(line);
success |= line.contains(success_marker);
Err(err) => {
warn(&format!("current_application() ERROR: {err}"));
Application::Other
}
}
success
}

pub fn launch_or_focus(application_name: &str) {
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ mod projects;
mod terminal;
mod tmux;
mod util;
mod wezterm;
mod wormhole;
#[macro_use]
pub mod pst;
pub use pst::*;

use std::convert::Infallible;
use std::net::SocketAddr;
Expand Down
Loading

0 comments on commit 76166c3

Please sign in to comment.