diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 6fd07174..2cf11196 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -93,6 +93,20 @@ mod util; mod viewport; impl OpenCADStudio { + fn sync_open_command_history(&mut self) { + if !self.command_line.history_open { + return; + } + let latest = self.command_line.history_plain_text(); + if self.history_content.text() == latest { + return; + } + use iced::widget::text_editor::{Action, Motion}; + self.history_content = iced::widget::text_editor::Content::with_text(&latest); + self.history_content + .perform(Action::Move(Motion::DocumentEnd)); + } + /// Close the active in-canvas modal (Plan B), mirroring what closing the /// old OS window did: a style editor discards its staged (un-applied) /// changes, and the ribbon tool that launched the dialog is de-highlighted. @@ -178,6 +192,7 @@ impl OpenCADStudio { } } let task = self.update_inner(msg); + self.sync_open_command_history(); // Close the document-level first-touch transaction started by // push_undo_snapshot at this message boundary. self.finish_all_pending_history(); @@ -1295,17 +1310,7 @@ impl OpenCADStudio { Message::CommandHistoryToggle => { self.command_line.toggle_history(); - // On open, snapshot the current log into the read-only editor - // buffer so it can be drag-selected across lines and copied. - if self.command_line.history_open { - use iced::widget::text_editor::{Action, Motion}; - self.history_content = iced::widget::text_editor::Content::with_text( - &self.command_line.history_plain_text(), - ); - // Scroll to the newest line (bottom), the most useful when - // opening the log to grab a recent error. - self.history_content.perform(Action::Move(Motion::DocumentEnd)); - } + self.sync_open_command_history(); Task::none() } diff --git a/src/ui/command_line.rs b/src/ui/command_line.rs index 085d33de..469881ac 100644 --- a/src/ui/command_line.rs +++ b/src/ui/command_line.rs @@ -5,7 +5,7 @@ use iced::time::Instant; use crate::app::Message; use crate::command::CmdOption; use iced::widget::{ - button, column, container, opaque, row, text, text_editor, text_input, tooltip, Space, + button, column, container, opaque, row, rule, text, text_editor, text_input, tooltip, Space, }; use iced::{Background, Border, Color, Element, Length, Theme}; @@ -361,11 +361,18 @@ impl CommandLine { // Only the most recent entries pushed within the last few // seconds show on the overlay. The dropdown button keeps the // full backlog reachable when the user actually wants it. - let visible: Vec<&HistoryEntry> = self + let mut visible: Vec<&HistoryEntry> = self .history .iter() .filter(|e| e.pinned || e.created_at.elapsed().as_secs_f32() < HISTORY_VISIBLE_SECS) .collect(); + // Keep the active prompt/options immediately above the input. Commands + // may emit informational lines while waiting for the next option; those + // lines belong above the pinned interaction row, not below it. + if let Some(index) = visible.iter().position(|entry| entry.pinned) { + let pinned = visible.remove(index); + visible.push(pinned); + } let start = visible.len().saturating_sub(4); let history_rows = visible[start..] .iter() @@ -602,7 +609,6 @@ impl CommandLine { .width(Length::Fill) .padding([2, 6]); let panel = container(column![header, log]) - .style(container::bordered_box) .width(Length::Fill) .padding([4, 0]); opaque(panel).into() @@ -610,9 +616,12 @@ impl CommandLine { container(column![]).height(0).into() }; - container(column![ - autocomplete, - dropdown, + // The transient recent lines and the full archive are two views of the + // same history. Showing both while the archive is open creates a fake + // second history region and visually disconnects the input row (#555). + let recent_history: Element<'a, Message> = if self.history_open { + container(column![]).height(0).into() + } else { container(history_rows) .style(|theme: &Theme| container::Style { background: Some(Background::Color( @@ -621,17 +630,25 @@ impl CommandLine { ..Default::default() }) .width(Length::Fill) - .padding([2, 0]), + .padding([2, 0]) + .into() + }; + let history_divider: Element<'a, Message> = if self.history_open { + rule::horizontal(1).into() + } else { + container(column![]).height(0).into() + }; + + container(column![ + autocomplete, + dropdown, + recent_history, + history_divider, container(input_row) .style(|theme: &Theme| { let palette = theme.extended_palette(); container::Style { background: Some(Background::Color(palette.background.weakest.color)), - border: Border { - color: palette.background.neutral.color, - width: 1.0, - radius: 3.0.into() - }, ..Default::default() } }) @@ -652,7 +669,8 @@ impl CommandLine { ..Default::default() } }) - .width(Length::Fixed(720.0)) + .width(Length::Fill) + .max_width(720.0) .into() } }