@@ -26,6 +26,7 @@ use terminal::{
};
use theme::{ActiveTheme, Theme, ThemeSettings};
use ui::{ParentElement, Tooltip};
+use util::ResultExt;
use workspace::Workspace;
use std::mem;
@@ -47,6 +48,7 @@ pub struct LayoutState {
hyperlink_tooltip: Option<AnyElement>,
gutter: Pixels,
block_below_cursor_element: Option<AnyElement>,
+ base_text_style: TextStyle,
}
/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
@@ -898,6 +900,7 @@ impl Element for TerminalElement {
hyperlink_tooltip,
gutter,
block_below_cursor_element,
+ base_text_style: text_style,
}
},
)
@@ -919,8 +922,14 @@ impl Element for TerminalElement {
let origin =
bounds.origin + Point::new(layout.gutter, px(0.)) - Point::new(px(0.), scroll_top);
+ let marked_text_cloned: Option<String> = {
+ let ime_state = self.terminal_view.read(cx);
+ ime_state.marked_text.clone()
+ };
+
let terminal_input_handler = TerminalInputHandler {
terminal: self.terminal.clone(),
+ terminal_view: self.terminal_view.clone(),
cursor_bounds: layout
.cursor
.as_ref()
@@ -938,7 +947,7 @@ impl Element for TerminalElement {
window.set_cursor_style(gpui::CursorStyle::IBeam, Some(&layout.hitbox));
}
- let cursor = layout.cursor.take();
+ let original_cursor = layout.cursor.take();
let hyperlink_tooltip = layout.hyperlink_tooltip.take();
let block_below_cursor_element = layout.block_below_cursor_element.take();
self.interactivity.paint(
@@ -988,8 +997,41 @@ impl Element for TerminalElement {
cell.paint(origin, &layout.dimensions, bounds, window, cx);
}
- if self.cursor_visible {
- if let Some(mut cursor) = cursor {
+ if let Some(text_to_mark) = &marked_text_cloned {
+ if !text_to_mark.is_empty() {
+ if let Some(cursor_layout) = &original_cursor {
+ let ime_position = cursor_layout.bounding_rect(origin).origin;
+ let mut ime_style = layout.base_text_style.clone();
+ ime_style.underline = Some(UnderlineStyle {
+ color: Some(ime_style.color),
+ thickness: px(1.0),
+ wavy: false,
+ });
+
+ let shaped_line = window
+ .text_system()
+ .shape_line(
+ text_to_mark.clone().into(),
+ ime_style.font_size.to_pixels(window.rem_size()),
+ &[TextRun {
+ len: text_to_mark.len(),
+ font: ime_style.font(),
+ color: ime_style.color,
+ background_color: None,
+ underline: ime_style.underline,
+ strikethrough: None,
+ }],
+ )
+ .unwrap();
+ shaped_line
+ .paint(ime_position, layout.dimensions.line_height, window, cx)
+ .log_err();
+ }
+ }
+ }
+
+ if self.cursor_visible && marked_text_cloned.is_none() {
+ if let Some(mut cursor) = original_cursor {
cursor.paint(origin, window, cx);
}
}
@@ -1017,6 +1059,7 @@ impl IntoElement for TerminalElement {
struct TerminalInputHandler {
terminal: Entity<Terminal>,
+ terminal_view: Entity<TerminalView>,
workspace: WeakEntity<Workspace>,
cursor_bounds: Option<Bounds<Pixels>>,
}
@@ -1044,8 +1087,12 @@ impl InputHandler for TerminalInputHandler {
}
}
- fn marked_text_range(&mut self, _: &mut Window, _: &mut App) -> Option<std::ops::Range<usize>> {
- None
+ fn marked_text_range(
+ &mut self,
+ _window: &mut Window,
+ cx: &mut App,
+ ) -> Option<std::ops::Range<usize>> {
+ self.terminal_view.read(cx).marked_text_range()
}
fn text_for_range(
@@ -1065,8 +1112,9 @@ impl InputHandler for TerminalInputHandler {
window: &mut Window,
cx: &mut App,
) {
- self.terminal.update(cx, |terminal, _| {
- terminal.input(text);
+ self.terminal_view.update(cx, |view, view_cx| {
+ view.clear_marked_text(view_cx);
+ view.commit_text(text, view_cx);
});
self.workspace
@@ -1082,22 +1130,37 @@ impl InputHandler for TerminalInputHandler {
fn replace_and_mark_text_in_range(
&mut self,
_range_utf16: Option<std::ops::Range<usize>>,
- _new_text: &str,
- _new_selected_range: Option<std::ops::Range<usize>>,
+ new_text: &str,
+ new_marked_range: Option<std::ops::Range<usize>>,
_window: &mut Window,
- _cx: &mut App,
+ cx: &mut App,
) {
+ if let Some(range) = new_marked_range {
+ self.terminal_view.update(cx, |view, view_cx| {
+ view.set_marked_text(new_text.to_string(), range, view_cx);
+ });
+ }
}
- fn unmark_text(&mut self, _window: &mut Window, _cx: &mut App) {}
+ fn unmark_text(&mut self, _window: &mut Window, cx: &mut App) {
+ self.terminal_view.update(cx, |view, view_cx| {
+ view.clear_marked_text(view_cx);
+ });
+ }
fn bounds_for_range(
&mut self,
- _range_utf16: std::ops::Range<usize>,
+ range_utf16: std::ops::Range<usize>,
_window: &mut Window,
- _cx: &mut App,
+ cx: &mut App,
) -> Option<Bounds<Pixels>> {
- self.cursor_bounds
+ let term_bounds = self.terminal_view.read(cx).terminal_bounds(cx);
+
+ let mut bounds = self.cursor_bounds?;
+ let offset_x = term_bounds.cell_width * range_utf16.start as f32;
+ bounds.origin.x += offset_x;
+
+ Some(bounds)
}
fn apple_press_and_hold_enabled(&mut self) -> bool {
@@ -52,7 +52,7 @@ use zed_actions::assistant::InlineAssist;
use std::{
cmp,
- ops::RangeInclusive,
+ ops::{Range, RangeInclusive},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
@@ -126,6 +126,8 @@ pub struct TerminalView {
scroll_handle: TerminalScrollHandle,
show_scrollbar: bool,
hide_scrollbar_task: Option<Task<()>>,
+ marked_text: Option<String>,
+ marked_range_utf16: Option<Range<usize>>,
_subscriptions: Vec<Subscription>,
_terminal_subscriptions: Vec<Subscription>,
}
@@ -218,6 +220,8 @@ impl TerminalView {
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
cwd_serialized: false,
+ marked_text: None,
+ marked_range_utf16: None,
_subscriptions: vec![
focus_in,
focus_out,
@@ -227,6 +231,45 @@ impl TerminalView {
}
}
+ /// Sets the marked (pre-edit) text from the IME.
+ pub(crate) fn set_marked_text(
+ &mut self,
+ text: String,
+ range: Range<usize>,
+ cx: &mut Context<Self>,
+ ) {
+ self.marked_text = Some(text);
+ self.marked_range_utf16 = Some(range);
+ cx.notify();
+ }
+
+ /// Gets the current marked range (UTF-16).
+ pub(crate) fn marked_text_range(&self) -> Option<Range<usize>> {
+ self.marked_range_utf16.clone()
+ }
+
+ /// Clears the marked (pre-edit) text state.
+ pub(crate) fn clear_marked_text(&mut self, cx: &mut Context<Self>) {
+ if self.marked_text.is_some() {
+ self.marked_text = None;
+ self.marked_range_utf16 = None;
+ cx.notify();
+ }
+ }
+
+ /// Commits (sends) the given text to the PTY. Called by InputHandler::replace_text_in_range.
+ pub(crate) fn commit_text(&mut self, text: &str, cx: &mut Context<Self>) {
+ if !text.is_empty() {
+ self.terminal.update(cx, |term, _| {
+ term.input(text.to_string());
+ });
+ }
+ }
+
+ pub(crate) fn terminal_bounds(&self, cx: &App) -> TerminalBounds {
+ self.terminal.read(cx).last_content().terminal_bounds
+ }
+
pub fn entity(&self) -> &Entity<Terminal> {
&self.terminal
}