From f985515141cbfd53bc7a3a7022a12d2dd6bd5f92 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 20 Jul 2022 16:45:27 -0700 Subject: [PATCH] Start work on new text input handling in Editor --- crates/collab/src/integration_tests.rs | 10 +- crates/editor/src/display_map.rs | 5 + crates/editor/src/editor.rs | 133 ++++++++++++++++++++----- crates/theme/src/theme.rs | 1 + crates/zed/src/zed.rs | 12 +-- styles/src/styleTree/editor.ts | 11 +- 6 files changed, 129 insertions(+), 43 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 358f016366b56db44c88d0219616d8df4aa2d4b5..a3dcd5fbce3942355da8cfacacb875b6bca0fd41 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -11,8 +11,8 @@ use client::{ }; use collections::{BTreeMap, HashMap, HashSet}; use editor::{ - self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename, - ToOffset, ToggleCodeActions, Undo, + self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset, + ToggleCodeActions, Undo, }; use futures::{channel::mpsc, Future, StreamExt as _}; use gpui::{ @@ -154,9 +154,7 @@ async fn test_share_project( // .await; // Edit the buffer as client B and see that edit as client A. - editor_b.update(cx_b, |editor, cx| { - editor.handle_input(&Input("ok, ".into()), cx) - }); + editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx)); buffer_a .condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents") .await; @@ -1751,7 +1749,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Type a completion trigger character as the guest. editor_b.update(cx_b, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input(&Input(".".into()), cx); + editor.handle_input(".", cx); cx.focus(&editor_b); }); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e4c888af4d51ea99fa4e78d048ac9191987baff3..0a0ac4a0e0970513b0cef1d0fcadde41967eed56 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -195,6 +195,11 @@ impl DisplayMap { .insert(Some(type_id), Arc::new((style, ranges))); } + pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range])> { + let highlights = self.text_highlights.get(&Some(type_id))?; + Some((highlights.0, &highlights.1)) + } + pub fn clear_text_highlights( &mut self, type_id: TypeId, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ac7b8a18668e7d02a18cb5f54d86b0bf2e456492..b3698b6ad93be38c472307bda19ca3d0190b44b2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -228,6 +228,7 @@ impl_internal_actions!(editor, [Scroll, Select, Jump]); enum DocumentHighlightRead {} enum DocumentHighlightWrite {} +enum InputComposition {} #[derive(Copy, Clone, PartialEq, Eq)] pub enum Direction { @@ -240,7 +241,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx)); cx.add_action(Editor::select); cx.add_action(Editor::cancel); - cx.add_action(Editor::handle_input); cx.add_action(Editor::newline); cx.add_action(Editor::backspace); cx.add_action(Editor::delete); @@ -1813,13 +1813,11 @@ impl Editor { cx.propagate_action(); } - pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext) { + pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { if !self.input_enabled { - cx.propagate_action(); return; } - let text = action.0.as_ref(); if !self.skip_autoclose_end(text, cx) { self.transact(cx, |this, cx| { if !this.surround_with_bracket_pair(text, cx) { @@ -5522,6 +5520,13 @@ impl Editor { cx.notify(); } + pub fn text_highlights<'a, T: 'static>( + &'a self, + cx: &'a AppContext, + ) -> Option<(HighlightStyle, &'a [Range])> { + self.display_map.read(cx).text_highlights(TypeId::of::()) + } + pub fn clear_text_highlights( &mut self, cx: &mut ViewContext, @@ -5871,6 +5876,80 @@ impl View for Editor { context } + + fn text_for_range(&self, range: Range, cx: &AppContext) -> Option { + Some( + self.buffer + .read(cx) + .read(cx) + .text_for_range(range) + .collect(), + ) + } + + fn selected_text_range(&self, cx: &AppContext) -> Option> { + Some(self.selections.newest(cx).range()) + } + + fn set_selected_text_range(&mut self, range: Range, cx: &mut ViewContext) { + self.change_selections(None, cx, |selections| selections.select_ranges([range])); + } + + fn marked_text_range(&self, cx: &AppContext) -> Option> { + let range = self.text_highlights::(cx)?.1.get(0)?; + Some(range.to_offset(&*self.buffer.read(cx).read(cx))) + } + + fn unmark_text(&mut self, cx: &mut ViewContext) { + self.clear_text_highlights::(cx); + } + + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut ViewContext, + ) { + self.transact(cx, |this, cx| { + if let Some(range) = range { + this.set_selected_text_range(range, cx); + } + this.handle_input(text, cx); + }); + } + + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + text: &str, + _new_selected_range: Option>, + cx: &mut ViewContext, + ) { + self.transact(cx, |this, cx| { + let range = range.or_else(|| { + let ranges = this.text_highlights::(cx)?.1; + let range = ranges.first()?; + let snapshot = this.buffer.read(cx).read(cx); + Some(range.to_offset(&*snapshot)) + }); + if let Some(range) = range { + this.set_selected_text_range(range, cx); + } + + let selection = this.selections.newest_anchor(); + let marked_range = { + let snapshot = this.buffer.read(cx).read(cx); + selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) + }; + this.highlight_text::( + vec![marked_range], + this.style(cx).composition_mark, + cx, + ); + + this.handle_input(text, cx); + }); + } } fn build_style( @@ -8241,9 +8320,9 @@ mod tests { // is pasted at each cursor. cx.set_state("|two one✅ four three six five |"); cx.update_editor(|e, cx| { - e.handle_input(&Input("( ".into()), cx); + e.handle_input("( ", cx); e.paste(&Paste, cx); - e.handle_input(&Input(") ".into()), cx); + e.handle_input(") ", cx); }); cx.assert_editor_state(indoc! {" ( one✅ @@ -8918,9 +8997,9 @@ mod tests { ]) }); - view.handle_input(&Input("{".to_string()), cx); - view.handle_input(&Input("{".to_string()), cx); - view.handle_input(&Input("{".to_string()), cx); + view.handle_input("{", cx); + view.handle_input("{", cx); + view.handle_input("{", cx); assert_eq!( view.text(cx), " @@ -8933,9 +9012,9 @@ mod tests { ); view.move_right(&MoveRight, cx); - view.handle_input(&Input("}".to_string()), cx); - view.handle_input(&Input("}".to_string()), cx); - view.handle_input(&Input("}".to_string()), cx); + view.handle_input("}", cx); + view.handle_input("}", cx); + view.handle_input("}", cx); assert_eq!( view.text(cx), " @@ -8948,8 +9027,8 @@ mod tests { ); view.undo(&Undo, cx); - view.handle_input(&Input("/".to_string()), cx); - view.handle_input(&Input("*".to_string()), cx); + view.handle_input("/", cx); + view.handle_input("*", cx); assert_eq!( view.text(cx), " @@ -8968,7 +9047,7 @@ mod tests { DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), ]) }); - view.handle_input(&Input("*".to_string()), cx); + view.handle_input("*", cx); assert_eq!( view.text(cx), " @@ -8986,7 +9065,7 @@ mod tests { view.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]) }); - view.handle_input(&Input("{".to_string()), cx); + view.handle_input("{", cx); assert_eq!( view.text(cx), " @@ -9002,7 +9081,7 @@ mod tests { view.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)]) }); - view.handle_input(&Input("{".to_string()), cx); + view.handle_input("{", cx); assert_eq!( view.text(cx), " @@ -9019,7 +9098,7 @@ mod tests { ); view.undo(&Undo, cx); - view.handle_input(&Input("[".to_string()), cx); + view.handle_input("[", cx); assert_eq!( view.text(cx), " @@ -9039,7 +9118,7 @@ mod tests { view.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]) }); - view.handle_input(&Input("[".to_string()), cx); + view.handle_input("[", cx); assert_eq!( view.text(cx), " @@ -9095,9 +9174,9 @@ mod tests { ]) }); - view.handle_input(&Input("{".to_string()), cx); - view.handle_input(&Input("{".to_string()), cx); - view.handle_input(&Input("{".to_string()), cx); + view.handle_input("{", cx); + view.handle_input("{", cx); + view.handle_input("{", cx); assert_eq!( view.text(cx), " @@ -9177,9 +9256,9 @@ mod tests { ]) }); - editor.handle_input(&Input("{".to_string()), cx); - editor.handle_input(&Input("{".to_string()), cx); - editor.handle_input(&Input("_".to_string()), cx); + editor.handle_input("{", cx); + editor.handle_input("{", cx); + editor.handle_input("_", cx); assert_eq!( editor.text(cx), " @@ -9905,7 +9984,7 @@ mod tests { ]) }); - view.handle_input(&Input("X".to_string()), cx); + view.handle_input("X", cx); assert_eq!(view.text(cx), "Xaaaa\nXbbbb"); assert_eq!( view.selections.ranges(cx), @@ -9945,7 +10024,7 @@ mod tests { assert_eq!(view.text(cx), expected_text); view.change_selections(None, cx, |s| s.select_ranges(selection_ranges)); - view.handle_input(&Input("X".to_string()), cx); + view.handle_input("X", cx); let (expected_text, expected_selections) = marked_text_ranges(indoc! {" aaaa diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 2299bc3477fa55fef6ab1d956c3646de408a16c8..ad013bc8602f478a88e33f524edbc42fbc6d3462 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -450,6 +450,7 @@ pub struct Editor { pub unnecessary_code_fade: f32, pub hover_popover: HoverPopover, pub link_definition: HighlightStyle, + pub composition_mark: HighlightStyle, pub jump_icon: Interactive, } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a7420c59f0f012dad9019532a6e23da9d2738e23..3a5725905d83c8da5a814b6d0f16dd4928b05de3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -920,11 +920,7 @@ mod tests { item.downcast::().unwrap() }); - cx.update(|cx| { - editor.update(cx, |editor, cx| { - editor.handle_input(&editor::Input("x".into()), cx) - }) - }); + cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input("x", cx))); app_state .fs .as_fake() @@ -971,7 +967,7 @@ mod tests { editor.language_at(0, cx).unwrap(), &languages::PLAIN_TEXT )); - editor.handle_input(&editor::Input("hi".into()), cx); + editor.handle_input("hi", cx); assert!(editor.is_dirty(cx)); }); @@ -997,7 +993,7 @@ mod tests { // Edit the file and save it again. This time, there is no filename prompt. editor.update(cx, |editor, cx| { - editor.handle_input(&editor::Input(" there".into()), cx); + editor.handle_input(" there", cx); assert_eq!(editor.is_dirty(cx.as_ref()), true); }); let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx)); @@ -1057,7 +1053,7 @@ mod tests { editor.language_at(0, cx).unwrap(), &languages::PLAIN_TEXT )); - editor.handle_input(&editor::Input("hi".into()), cx); + editor.handle_input("hi", cx); assert!(editor.is_dirty(cx.as_ref())); }); diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 8031a229c4a81b4f5de26aa35b614a03cb9eb20d..222cf52d60fb46656aa6b517223a9fc35a44f829 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -2,6 +2,7 @@ import Theme from "../themes/common/theme"; import { backgroundColor, border, + borderColor, iconColor, player, popoverShadow, @@ -138,8 +139,8 @@ export default function editor(theme: Theme) { invalidHintDiagnostic: diagnostic(theme, "muted"), invalidInformationDiagnostic: diagnostic(theme, "muted"), invalidWarningDiagnostic: diagnostic(theme, "muted"), - hover_popover: hoverPopover(theme), - link_definition: { + hoverPopover: hoverPopover(theme), + linkDefinition: { color: theme.syntax.linkUri.color, underline: theme.syntax.linkUri.underline, }, @@ -159,6 +160,12 @@ export default function editor(theme: Theme) { background: backgroundColor(theme, "on500", "base"), } }, + compositionMark: { + underline: { + thickness: 1.0, + color: borderColor(theme, "active") + }, + }, syntax, }; }