feat: redesign launcher palette and stabilize AI hook warnings
This commit is contained in:
1 parent
0d395f2162
commit
4e4b3be7fd
7 files changed
+743
-220
No files matched your search
@@ -1036,15 +1036,35 @@ export default function (pi: ExtensionAPI) {
|
||||
}
|
||||
|
||||
/// Absolute path of the bridge exe.
|
||||
///
|
||||
/// The helper is an optional runtime asset, so a development checkout or
|
||||
/// an incomplete standalone directory is a valid state. `helper_path()`
|
||||
/// is called from several self-healing paths and from the pipe bootstrap;
|
||||
/// logging on every probe turns that state into an apparent infinite
|
||||
/// warning loop when a config watcher is busy. Keep the state transition
|
||||
/// noisy once, but make repeated probes silent until the helper is found
|
||||
/// again (or removed later).
|
||||
static HELPER_MISSING_ANNOUNCED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn helper_path() -> Option<PathBuf> {
|
||||
let exe = std::env::current_exe().ok()?;
|
||||
let helper = helper_path_from_exe(&exe);
|
||||
if helper.is_none() {
|
||||
log::warn!(
|
||||
"ai_hook: nebula-hook.exe missing from runtime/ and executable directory; AI integrations not installed"
|
||||
);
|
||||
match helper {
|
||||
Some(path) => {
|
||||
// Permit a later installation/removal to be reported once on
|
||||
// the next state transition rather than caching a stale path.
|
||||
HELPER_MISSING_ANNOUNCED.store(false, Ordering::Relaxed);
|
||||
Some(path)
|
||||
},
|
||||
None => {
|
||||
if !HELPER_MISSING_ANNOUNCED.swap(true, Ordering::Relaxed) {
|
||||
log::warn!(
|
||||
"ai_hook: nebula-hook.exe missing from runtime/ and executable directory; AI integrations not installed"
|
||||
);
|
||||
}
|
||||
None
|
||||
},
|
||||
}
|
||||
helper
|
||||
}
|
||||
|
||||
fn helper_path_from_exe(exe: &Path) -> Option<PathBuf> {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4446,11 +4446,21 @@ impl Display {
|
||||
/// any config profiles. Detection runs once and is cached — the chevron
|
||||
/// beside the "+" opens this — the familiar profile menu.
|
||||
pub fn open_shell_menu(&mut self, profiles: &[crate::config::ui_config::Profile]) {
|
||||
let ssh_rows: Vec<(String, String)> = self
|
||||
.nebula_ssh_hosts
|
||||
.iter()
|
||||
.map(|host| {
|
||||
let label =
|
||||
self.nebula_ssh_labels.get(host).cloned().unwrap_or_else(|| host.clone());
|
||||
(label, host.clone())
|
||||
})
|
||||
.collect();
|
||||
let shells =
|
||||
self.nebula_detected_shells.get_or_insert_with(crate::shell_detect::detect_shells);
|
||||
let default_shell =
|
||||
self.nebula_shell_id.as_deref().unwrap_or_else(|| self.nebula_shell.settings_value());
|
||||
self.nebula_palette.set_shell_menu(shells, profiles, default_shell);
|
||||
self.nebula_palette.set_ssh_hosts(&ssh_rows);
|
||||
self.nebula_palette.open_profiles();
|
||||
self.pending_update.dirty = true;
|
||||
}
|
||||
@@ -4551,6 +4561,25 @@ impl Display {
|
||||
self.pending_update.dirty = true;
|
||||
}
|
||||
|
||||
pub fn palette_tab(&mut self, delta: i32) {
|
||||
if !self.nebula_palette.cycle_launcher_filter(delta) {
|
||||
self.palette_move(delta);
|
||||
return;
|
||||
}
|
||||
self.pending_update.dirty = true;
|
||||
}
|
||||
|
||||
pub fn palette_select_launcher_filter(
|
||||
&mut self,
|
||||
filter: command_palette::LauncherFilter,
|
||||
) -> bool {
|
||||
if self.nebula_palette.set_launcher_filter(filter) {
|
||||
self.pending_update.dirty = true;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn palette_scroll_by(&mut self, rows: i32, max_rows: usize) -> bool {
|
||||
if self.nebula_palette.scroll_by(rows, max_rows) {
|
||||
self.pending_update.dirty = true;
|
||||
@@ -4613,8 +4642,13 @@ impl Display {
|
||||
|
||||
/// Update palette hover state. `row` is the visual row index, or `None` when
|
||||
/// the mouse left the palette area.
|
||||
pub fn palette_hover(&mut self, pos: (f32, f32), row: Option<usize>) -> bool {
|
||||
if self.nebula_palette.pointer_hover(pos, row) {
|
||||
pub fn palette_hover(
|
||||
&mut self,
|
||||
pos: (f32, f32),
|
||||
row: Option<usize>,
|
||||
chip: Option<command_palette::LauncherFilter>,
|
||||
) -> bool {
|
||||
if self.nebula_palette.pointer_hover(pos, row, chip) {
|
||||
self.pending_update.dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -367,7 +367,9 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
||||
let layout = self.ctx.display().command_palette_layout();
|
||||
let (px, py, pw, ph) = layout.panel;
|
||||
if x >= px && x < px + pw && y >= py && y < py + ph {
|
||||
if self.ctx.display().palette_scrollbar_press(x, y, &layout) {
|
||||
if let Some(filter) = layout.chip_at(x, y) {
|
||||
self.ctx.display().palette_select_launcher_filter(filter);
|
||||
} else if self.ctx.display().palette_scrollbar_press(x, y, &layout) {
|
||||
self.ctx.window().set_mouse_cursor(winit::window::CursorIcon::Grabbing);
|
||||
} else if let Some(row) = layout.row_at(x, y) {
|
||||
if let Some(action) = self.ctx.display().palette_click(row, layout.max_rows)
|
||||
|
||||
@@ -533,7 +533,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
||||
}
|
||||
},
|
||||
Key::Named(NamedKey::Tab) => {
|
||||
self.ctx.display().palette_move(if mods.shift_key() { -1 } else { 1 });
|
||||
self.ctx.display().palette_tab(if mods.shift_key() { -1 } else { 1 });
|
||||
},
|
||||
Key::Named(NamedKey::ArrowDown) => self.ctx.display().palette_move(1),
|
||||
Key::Named(NamedKey::ArrowUp) => self.ctx.display().palette_move(-1),
|
||||
@@ -923,6 +923,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
||||
name: shell.name.clone(),
|
||||
shell: shell.shell(),
|
||||
}),
|
||||
LaunchSsh(host) => self.ctx.nebula_tab(TabRequest::NewSsh(host)),
|
||||
SetDefaultShell(shell) => self.ctx.display().set_default_shell(&shell),
|
||||
SetDefaultProfile(profile) => self.ctx.display().set_default_profile(&profile),
|
||||
NewAtDirectory(path) => self.ctx.nebula_tab(TabRequest::NewAtDirectory(path)),
|
||||
|
||||
@@ -280,17 +280,19 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
||||
}
|
||||
let over_scrollbar =
|
||||
layout.scrollbar.is_some_and(|scrollbar| scrollbar.hit_test(px, py));
|
||||
let hover_chip = if over_scrollbar { None } else { layout.chip_at(px, py) };
|
||||
// Hover detection rides the layout's per-row rects (`row_at`): the
|
||||
// picker's card geometry is non-uniform (hero card, section gaps),
|
||||
// so dividing by row_h would light up captions and gaps too. The
|
||||
// scrollbar's widened hit target owns its pixels and suppresses row hover.
|
||||
let hover_row = if over_scrollbar { None } else { layout.row_at(px, py) };
|
||||
if self.ctx.display().palette_hover((px, py), hover_row) {
|
||||
let hover_row =
|
||||
if over_scrollbar || hover_chip.is_some() { None } else { layout.row_at(px, py) };
|
||||
if self.ctx.display().palette_hover((px, py), hover_row, hover_chip) {
|
||||
self.ctx.mark_dirty();
|
||||
}
|
||||
self.ctx.window().set_mouse_cursor(if over_scrollbar {
|
||||
CursorIcon::Grab
|
||||
} else if hover_row.is_some() {
|
||||
} else if hover_row.is_some() || hover_chip.is_some() {
|
||||
CursorIcon::Pointer
|
||||
} else {
|
||||
CursorIcon::Default
|
||||
@@ -474,9 +476,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
||||
self.ctx.window().set_mouse_cursor(CursorIcon::Grab);
|
||||
return;
|
||||
}
|
||||
if self.ctx.image_view().is_some()
|
||||
&& rect_contains(image_area, x as f32, y as f32)
|
||||
{
|
||||
if self.ctx.image_view().is_some() && rect_contains(image_area, x as f32, y as f32) {
|
||||
self.ctx.window().set_mouse_cursor(CursorIcon::Grab);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,15 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Load command line options.
|
||||
let options = Options::new();
|
||||
|
||||
#[cfg(windows)]
|
||||
if options.subcommands.is_none() && env::var_os("NEBULA_DETACHED_LAUNCH").is_some() {
|
||||
// 独立验证实例必须在创建窗口和 ConPTY 前脱离短生命周期的启动控制台,
|
||||
// 否则启动器退出会连带关闭窗口,只留下后台会话进程。
|
||||
unsafe {
|
||||
FreeConsole();
|
||||
}
|
||||
}
|
||||
|
||||
match options.subcommands {
|
||||
#[cfg(unix)]
|
||||
Some(Subcommands::Msg(options)) => msg(options)?,
|
||||
|
||||
Reference in new issue
Block a user