Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
Michael Sloan
a1a6031c6a
Close stdin 2025-08-19 16:08:12 -06:00
Michael Sloan
2d20b5d850
Log command that is run 2025-08-19 15:53:09 -06:00
Michael Sloan
11ad0b5793
Rerun command if it is a file and the file changes 2025-08-19 15:47:14 -06:00
Michael Sloan
2755cd8ec7
Add ZED_SELECTION_CHANGE_CMD to run a command on selection change 2025-08-19 14:46:16 -06:00
5 changed files with 147 additions and 8 deletions

2
Cargo.lock generated
View file

@ -5070,6 +5070,7 @@ dependencies = [
"multi_buffer",
"ordered-float 2.10.1",
"parking_lot",
"postage",
"pretty_assertions",
"project",
"rand 0.8.5",
@ -20462,6 +20463,7 @@ dependencies = [
"parking_lot",
"paths",
"picker",
"postage",
"pretty_assertions",
"profiling",
"project",

View file

@ -92,6 +92,7 @@ uuid.workspace = true
workspace.workspace = true
zed_actions.workspace = true
workspace-hack.workspace = true
postage.workspace = true
[dev-dependencies]
ctor.workspace = true

View file

@ -176,17 +176,15 @@ use snippet::Snippet;
use std::{
any::TypeId,
borrow::Cow,
cell::OnceCell,
cell::RefCell,
cell::{OnceCell, RefCell},
cmp::{self, Ordering, Reverse},
iter::Peekable,
mem,
num::NonZeroU32,
ops::Not,
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
sync::{Arc, LazyLock},
time::{Duration, Instant},
};
use sum_tree::TreeMap;
@ -237,6 +235,21 @@ pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LastCursorPosition {
pub path: PathBuf,
pub worktree_path: Arc<Path>,
pub point: Point,
}
pub static LAST_CURSOR_POSITION_WATCH: LazyLock<(
Mutex<postage::watch::Sender<Option<LastCursorPosition>>>,
postage::watch::Receiver<Option<LastCursorPosition>>,
)> = LazyLock::new(|| {
let (sender, receiver) = postage::watch::channel();
(Mutex::new(sender), receiver)
});
pub type RenderDiffHunkControlsFn = Arc<
dyn Fn(
u32,
@ -3018,10 +3031,28 @@ impl Editor {
let new_cursor_position = newest_selection.head();
let selection_start = newest_selection.start;
let new_cursor_point = new_cursor_position.to_point(buffer);
if let Some(project) = self.project()
&& let Some((path, worktree_path)) =
self.file_at(new_cursor_point, cx).and_then(|file| {
file.as_local().and_then(|file| {
let worktree =
project.read(cx).worktree_for_id(file.worktree_id(cx), cx)?;
Some((file.abs_path(cx), worktree.read(cx).abs_path()))
})
})
{
*LAST_CURSOR_POSITION_WATCH.0.lock().borrow_mut() = Some(LastCursorPosition {
path,
worktree_path,
point: new_cursor_point,
});
}
if effects.nav_history.is_none() || effects.nav_history == Some(true) {
self.push_to_nav_history(
*old_cursor_position,
Some(new_cursor_position.to_point(buffer)),
Some(new_cursor_point),
false,
effects.nav_history == Some(true),
cx,

View file

@ -164,6 +164,7 @@ zed_actions.workspace = true
zeta.workspace = true
zlog.workspace = true
zlog_settings.workspace = true
postage.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true

View file

@ -14,9 +14,10 @@ use editor::Editor;
use extension::ExtensionHostProxy;
use extension_host::ExtensionStore;
use fs::{Fs, RealFs};
use futures::{StreamExt, channel::oneshot, future};
use futures::{FutureExt, StreamExt, channel::oneshot, future, select_biased};
use git::GitHostingProviderRegistry;
use gpui::{App, AppContext as _, Application, AsyncApp, Focusable as _, UpdateGlobal as _};
use postage::stream::Stream as _;
use gpui_tokio::Tokio;
use http_client::{Url, read_proxy_from_env};
@ -37,7 +38,7 @@ use std::{
env,
io::{self, IsTerminal},
path::{Path, PathBuf},
process,
process::{self, Stdio},
sync::Arc,
};
use theme::{
@ -732,6 +733,8 @@ pub fn main() {
}
}
// todo! cleanup
let fs = app_state.fs.clone();
let app_state = app_state.clone();
crate::zed::component_preview::init(app_state.clone(), cx);
@ -747,6 +750,107 @@ pub fn main() {
}
})
.detach();
if let Ok(selection_change_command) = env::var("ZED_SELECTION_CHANGE_CMD") {
log::info!(
"Will run {} when the selection changes",
selection_change_command
);
let mut cursor_receiver = editor::LAST_CURSOR_POSITION_WATCH.1.clone();
cx.background_spawn(async move {
// Set up file watcher for the command file
let command_path = PathBuf::from(&selection_change_command);
let mut file_changes = if command_path.exists() {
let (events, _) = fs
.watch(&command_path, std::time::Duration::from_millis(100))
.await;
Some(events)
} else {
log::warn!(
"Command file {} does not exist, only watching selection changes",
command_path.display()
);
None
};
loop {
select_biased! {
// Handle cursor position changes
cursor_update = cursor_receiver.recv().fuse() => {
if cursor_update.is_none() {
// Cursor watcher ended
log::warn!("Cursor watcher for {} ended", command_path.display());
break;
}
},
// Handle file changes to the command file
file_change = async {
if let Some(ref mut events) = file_changes {
events.next().await
} else {
future::pending().await
}
}.fuse() => {
if file_change.is_none() {
// File watcher ended
log::warn!("File watcher for {} ended", command_path.display());
file_changes = None;
}
}
}
// TODO: Could be more efficient
let Some(mut cursor) = cursor_receiver.borrow().clone() else {
continue;
};
loop {
let cursor_position_arg = format!(
"{}:{}:{}",
cursor.path.display(),
cursor.point.row + 1,
cursor.point.column + 1
);
log::info!(
"Running {} {} {}",
selection_change_command,
cursor.worktree_path.display(),
cursor_position_arg
);
let status = smol::process::Command::new(&selection_change_command)
.arg(cursor.worktree_path.as_ref())
.arg(cursor_position_arg)
.stdin(Stdio::null())
// todo! It'd be better to distinguish the output in logs.
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.await;
match status {
Ok(status) => {
if !status.success() {
log::error!("Command failed with status {}", status);
}
}
Err(err) => {
log::error!("Command failed with error {}", err);
}
}
let Some(new_cursor) = cursor_receiver.borrow().clone() else {
break;
};
if new_cursor == cursor {
break;
}
cursor = new_cursor;
}
}
})
.detach();
}
});
}