diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index c63c531cebddcfa1f807ee5460a54b1976d9c175..29742f365120a63df882e8f1bc77dba67264e1df 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -37,9 +37,9 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context, - DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, Hsla, - Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, - WeakView, WindowContext, + DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, + HighlightStyle, Hsla, InputHandler, Model, Pixels, PlatformInputHandler, Render, Styled, + Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -56,6 +56,7 @@ use language::{ use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use lsp::{DiagnosticSeverity, Documentation, LanguageServerId}; use movement::TextLayoutDetails; +use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, @@ -67,7 +68,7 @@ use rpc::proto::*; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; -use selections_collection::{MutableSelectionsCollection, SelectionsCollection}; +use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use smallvec::SmallVec; @@ -82,7 +83,7 @@ use std::{ }; pub use sum_tree::Bias; use sum_tree::TreeMap; -use text::Rope; +use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; @@ -2769,197 +2770,197 @@ impl Editor { // cx.propagate(); // } - // pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { - // let text: Arc = text.into(); + pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { + let text: Arc = text.into(); - // if self.read_only { - // return; - // } + if self.read_only { + return; + } - // let selections = self.selections.all_adjusted(cx); - // let mut brace_inserted = false; - // let mut edits = Vec::new(); - // let mut new_selections = Vec::with_capacity(selections.len()); - // let mut new_autoclose_regions = Vec::new(); - // let snapshot = self.buffer.read(cx).read(cx); + let selections = self.selections.all_adjusted(cx); + let mut brace_inserted = false; + let mut edits = Vec::new(); + let mut new_selections = Vec::with_capacity(selections.len()); + let mut new_autoclose_regions = Vec::new(); + let snapshot = self.buffer.read(cx).read(cx); - // for (selection, autoclose_region) in - // self.selections_with_autoclose_regions(selections, &snapshot) - // { - // if let Some(scope) = snapshot.language_scope_at(selection.head()) { - // // Determine if the inserted text matches the opening or closing - // // bracket of any of this language's bracket pairs. - // let mut bracket_pair = None; - // let mut is_bracket_pair_start = false; - // if !text.is_empty() { - // // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) - // // and they are removing the character that triggered IME popup. - // for (pair, enabled) in scope.brackets() { - // if enabled && pair.close && pair.start.ends_with(text.as_ref()) { - // bracket_pair = Some(pair.clone()); - // is_bracket_pair_start = true; - // break; - // } else if pair.end.as_str() == text.as_ref() { - // bracket_pair = Some(pair.clone()); - // break; - // } - // } - // } + for (selection, autoclose_region) in + self.selections_with_autoclose_regions(selections, &snapshot) + { + if let Some(scope) = snapshot.language_scope_at(selection.head()) { + // Determine if the inserted text matches the opening or closing + // bracket of any of this language's bracket pairs. + let mut bracket_pair = None; + let mut is_bracket_pair_start = false; + if !text.is_empty() { + // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) + // and they are removing the character that triggered IME popup. + for (pair, enabled) in scope.brackets() { + if enabled && pair.close && pair.start.ends_with(text.as_ref()) { + bracket_pair = Some(pair.clone()); + is_bracket_pair_start = true; + break; + } else if pair.end.as_str() == text.as_ref() { + bracket_pair = Some(pair.clone()); + break; + } + } + } - // if let Some(bracket_pair) = bracket_pair { - // if selection.is_empty() { - // if is_bracket_pair_start { - // let prefix_len = bracket_pair.start.len() - text.len(); - - // // If the inserted text is a suffix of an opening bracket and the - // // selection is preceded by the rest of the opening bracket, then - // // insert the closing bracket. - // let following_text_allows_autoclose = snapshot - // .chars_at(selection.start) - // .next() - // .map_or(true, |c| scope.should_autoclose_before(c)); - // let preceding_text_matches_prefix = prefix_len == 0 - // || (selection.start.column >= (prefix_len as u32) - // && snapshot.contains_str_at( - // Point::new( - // selection.start.row, - // selection.start.column - (prefix_len as u32), - // ), - // &bracket_pair.start[..prefix_len], - // )); - // if following_text_allows_autoclose && preceding_text_matches_prefix { - // let anchor = snapshot.anchor_before(selection.end); - // new_selections.push((selection.map(|_| anchor), text.len())); - // new_autoclose_regions.push(( - // anchor, - // text.len(), - // selection.id, - // bracket_pair.clone(), - // )); - // edits.push(( - // selection.range(), - // format!("{}{}", text, bracket_pair.end).into(), - // )); - // brace_inserted = true; - // continue; - // } - // } + if let Some(bracket_pair) = bracket_pair { + if selection.is_empty() { + if is_bracket_pair_start { + let prefix_len = bracket_pair.start.len() - text.len(); + + // If the inserted text is a suffix of an opening bracket and the + // selection is preceded by the rest of the opening bracket, then + // insert the closing bracket. + let following_text_allows_autoclose = snapshot + .chars_at(selection.start) + .next() + .map_or(true, |c| scope.should_autoclose_before(c)); + let preceding_text_matches_prefix = prefix_len == 0 + || (selection.start.column >= (prefix_len as u32) + && snapshot.contains_str_at( + Point::new( + selection.start.row, + selection.start.column - (prefix_len as u32), + ), + &bracket_pair.start[..prefix_len], + )); + if following_text_allows_autoclose && preceding_text_matches_prefix { + let anchor = snapshot.anchor_before(selection.end); + new_selections.push((selection.map(|_| anchor), text.len())); + new_autoclose_regions.push(( + anchor, + text.len(), + selection.id, + bracket_pair.clone(), + )); + edits.push(( + selection.range(), + format!("{}{}", text, bracket_pair.end).into(), + )); + brace_inserted = true; + continue; + } + } - // if let Some(region) = autoclose_region { - // // If the selection is followed by an auto-inserted closing bracket, - // // then don't insert that closing bracket again; just move the selection - // // past the closing bracket. - // let should_skip = selection.end == region.range.end.to_point(&snapshot) - // && text.as_ref() == region.pair.end.as_str(); - // if should_skip { - // let anchor = snapshot.anchor_after(selection.end); - // new_selections - // .push((selection.map(|_| anchor), region.pair.end.len())); - // continue; - // } - // } - // } - // // If an opening bracket is 1 character long and is typed while - // // text is selected, then surround that text with the bracket pair. - // else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { - // edits.push((selection.start..selection.start, text.clone())); - // edits.push(( - // selection.end..selection.end, - // bracket_pair.end.as_str().into(), - // )); - // brace_inserted = true; - // new_selections.push(( - // Selection { - // id: selection.id, - // start: snapshot.anchor_after(selection.start), - // end: snapshot.anchor_before(selection.end), - // reversed: selection.reversed, - // goal: selection.goal, - // }, - // 0, - // )); - // continue; - // } - // } - // } + if let Some(region) = autoclose_region { + // If the selection is followed by an auto-inserted closing bracket, + // then don't insert that closing bracket again; just move the selection + // past the closing bracket. + let should_skip = selection.end == region.range.end.to_point(&snapshot) + && text.as_ref() == region.pair.end.as_str(); + if should_skip { + let anchor = snapshot.anchor_after(selection.end); + new_selections + .push((selection.map(|_| anchor), region.pair.end.len())); + continue; + } + } + } + // If an opening bracket is 1 character long and is typed while + // text is selected, then surround that text with the bracket pair. + else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { + edits.push((selection.start..selection.start, text.clone())); + edits.push(( + selection.end..selection.end, + bracket_pair.end.as_str().into(), + )); + brace_inserted = true; + new_selections.push(( + Selection { + id: selection.id, + start: snapshot.anchor_after(selection.start), + end: snapshot.anchor_before(selection.end), + reversed: selection.reversed, + goal: selection.goal, + }, + 0, + )); + continue; + } + } + } - // // If not handling any auto-close operation, then just replace the selected - // // text with the given input and move the selection to the end of the - // // newly inserted text. - // let anchor = snapshot.anchor_after(selection.end); - // new_selections.push((selection.map(|_| anchor), 0)); - // edits.push((selection.start..selection.end, text.clone())); - // } + // If not handling any auto-close operation, then just replace the selected + // text with the given input and move the selection to the end of the + // newly inserted text. + let anchor = snapshot.anchor_after(selection.end); + new_selections.push((selection.map(|_| anchor), 0)); + edits.push((selection.start..selection.end, text.clone())); + } - // drop(snapshot); - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, this.autoindent_mode.clone(), cx); - // }); + drop(snapshot); + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, this.autoindent_mode.clone(), cx); + }); - // let new_anchor_selections = new_selections.iter().map(|e| &e.0); - // let new_selection_deltas = new_selections.iter().map(|e| e.1); - // let snapshot = this.buffer.read(cx).read(cx); - // let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) - // .zip(new_selection_deltas) - // .map(|(selection, delta)| Selection { - // id: selection.id, - // start: selection.start + delta, - // end: selection.end + delta, - // reversed: selection.reversed, - // goal: SelectionGoal::None, - // }) - // .collect::>(); + let new_anchor_selections = new_selections.iter().map(|e| &e.0); + let new_selection_deltas = new_selections.iter().map(|e| e.1); + let snapshot = this.buffer.read(cx).read(cx); + let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) + .zip(new_selection_deltas) + .map(|(selection, delta)| Selection { + id: selection.id, + start: selection.start + delta, + end: selection.end + delta, + reversed: selection.reversed, + goal: SelectionGoal::None, + }) + .collect::>(); - // let mut i = 0; - // for (position, delta, selection_id, pair) in new_autoclose_regions { - // let position = position.to_offset(&snapshot) + delta; - // let start = snapshot.anchor_before(position); - // let end = snapshot.anchor_after(position); - // while let Some(existing_state) = this.autoclose_regions.get(i) { - // match existing_state.range.start.cmp(&start, &snapshot) { - // Ordering::Less => i += 1, - // Ordering::Greater => break, - // Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { - // Ordering::Less => i += 1, - // Ordering::Equal => break, - // Ordering::Greater => break, - // }, - // } - // } - // this.autoclose_regions.insert( - // i, - // AutocloseRegion { - // selection_id, - // range: start..end, - // pair, - // }, - // ); - // } + let mut i = 0; + for (position, delta, selection_id, pair) in new_autoclose_regions { + let position = position.to_offset(&snapshot) + delta; + let start = snapshot.anchor_before(position); + let end = snapshot.anchor_after(position); + while let Some(existing_state) = this.autoclose_regions.get(i) { + match existing_state.range.start.cmp(&start, &snapshot) { + Ordering::Less => i += 1, + Ordering::Greater => break, + Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { + Ordering::Less => i += 1, + Ordering::Equal => break, + Ordering::Greater => break, + }, + } + } + this.autoclose_regions.insert( + i, + AutocloseRegion { + selection_id, + range: start..end, + pair, + }, + ); + } - // drop(snapshot); - // let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + drop(snapshot); + let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - // if !brace_inserted && EditorSettings>(cx).use_on_type_format { - // if let Some(on_type_format_task) = - // this.trigger_on_type_formatting(text.to_string(), cx) - // { - // on_type_format_task.detach_and_log_err(cx); - // } - // } + if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format { + if let Some(on_type_format_task) = + this.trigger_on_type_formatting(text.to_string(), cx) + { + on_type_format_task.detach_and_log_err(cx); + } + } - // if had_active_copilot_suggestion { - // this.refresh_copilot_suggestions(true, cx); - // if !this.has_active_copilot_suggestion(cx) { - // this.trigger_completion_on_input(&text, cx); - // } - // } else { - // this.trigger_completion_on_input(&text, cx); - // this.refresh_copilot_suggestions(true, cx); - // } - // }); - // } + if had_active_copilot_suggestion { + this.refresh_copilot_suggestions(true, cx); + if !this.has_active_copilot_suggestion(cx) { + this.trigger_completion_on_input(&text, cx); + } + } else { + this.trigger_completion_on_input(&text, cx); + this.refresh_copilot_suggestions(true, cx); + } + }); + } // pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { // self.transact(cx, |this, cx| { @@ -3259,22 +3260,22 @@ impl Editor { }); } - // fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - // if !EditorSettings>(cx).show_completions_on_input { - // return; - // } + fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { + if !EditorSettings::get_global(cx).show_completions_on_input { + return; + } - // let selection = self.selections.newest_anchor(); - // if self - // .buffer - // .read(cx) - // .is_completion_trigger(selection.head(), text, cx) - // { - // self.show_completions(&ShowCompletions, cx); - // } else { - // self.hide_context_menu(cx); - // } - // } + let selection = self.selections.newest_anchor(); + if self + .buffer + .read(cx) + .is_completion_trigger(selection.head(), text, cx) + { + self.show_completions(&ShowCompletions, cx); + } else { + self.hide_context_menu(cx); + } + } // /// If any empty selections is touching the start of its innermost containing autoclose // /// region, expand it to select the brackets. @@ -3305,37 +3306,37 @@ impl Editor { // self.change_selections(None, cx, |selections| selections.select(new_selections)); // } - // /// Iterate the given selections, and for each one, find the smallest surrounding - // /// autoclose region. This uses the ordering of the selections and the autoclose - // /// regions to avoid repeated comparisons. - // fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( - // &'a self, - // selections: impl IntoIterator>, - // buffer: &'a MultiBufferSnapshot, - // ) -> impl Iterator, Option<&'a AutocloseRegion>)> { - // let mut i = 0; - // let mut regions = self.autoclose_regions.as_slice(); - // selections.into_iter().map(move |selection| { - // let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); - - // let mut enclosing = None; - // while let Some(pair_state) = regions.get(i) { - // if pair_state.range.end.to_offset(buffer) < range.start { - // regions = ®ions[i + 1..]; - // i = 0; - // } else if pair_state.range.start.to_offset(buffer) > range.end { - // break; - // } else { - // if pair_state.selection_id == selection.id { - // enclosing = Some(pair_state); - // } - // i += 1; - // } - // } + /// Iterate the given selections, and for each one, find the smallest surrounding + /// autoclose region. This uses the ordering of the selections and the autoclose + /// regions to avoid repeated comparisons. + fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( + &'a self, + selections: impl IntoIterator>, + buffer: &'a MultiBufferSnapshot, + ) -> impl Iterator, Option<&'a AutocloseRegion>)> { + let mut i = 0; + let mut regions = self.autoclose_regions.as_slice(); + selections.into_iter().map(move |selection| { + let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + + let mut enclosing = None; + while let Some(pair_state) = regions.get(i) { + if pair_state.range.end.to_offset(buffer) < range.start { + regions = ®ions[i + 1..]; + i = 0; + } else if pair_state.range.start.to_offset(buffer) > range.end { + break; + } else { + if pair_state.selection_id == selection.id { + enclosing = Some(pair_state); + } + i += 1; + } + } - // (selection.clone(), enclosing) - // }) - // } + (selection.clone(), enclosing) + }) + } /// Remove any autoclose regions that no longer contain their selection. fn invalidate_autoclose_regions( @@ -3537,51 +3538,51 @@ impl Editor { cx.notify(); } - // fn trigger_on_type_formatting( - // &self, - // input: String, - // cx: &mut ViewContext, - // ) -> Option>> { - // if input.len() != 1 { - // return None; - // } + fn trigger_on_type_formatting( + &self, + input: String, + cx: &mut ViewContext, + ) -> Option>> { + if input.len() != 1 { + return None; + } - // let project = self.project.as_ref()?; - // let position = self.selections.newest_anchor().head(); - // let (buffer, buffer_position) = self - // .buffer - // .read(cx) - // .text_anchor_for_position(position.clone(), cx)?; - - // // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, - // // hence we do LSP request & edit on host side only — add formats to host's history. - // let push_to_lsp_host_history = true; - // // If this is not the host, append its history with new edits. - // let push_to_client_history = project.read(cx).is_remote(); - - // let on_type_formatting = project.update(cx, |project, cx| { - // project.on_type_format( - // buffer.clone(), - // buffer_position, - // input, - // push_to_lsp_host_history, - // cx, - // ) - // }); - // Some(cx.spawn(|editor, mut cx| async move { - // if let Some(transaction) = on_type_formatting.await? { - // if push_to_client_history { - // buffer.update(&mut cx, |buffer, _| { - // buffer.push_transaction(transaction, Instant::now()); - // }); - // } - // editor.update(&mut cx, |editor, cx| { - // editor.refresh_document_highlights(cx); - // })?; - // } - // Ok(()) - // })) - // } + let project = self.project.as_ref()?; + let position = self.selections.newest_anchor().head(); + let (buffer, buffer_position) = self + .buffer + .read(cx) + .text_anchor_for_position(position.clone(), cx)?; + + // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, + // hence we do LSP request & edit on host side only — add formats to host's history. + let push_to_lsp_host_history = true; + // If this is not the host, append its history with new edits. + let push_to_client_history = project.read(cx).is_remote(); + + let on_type_formatting = project.update(cx, |project, cx| { + project.on_type_format( + buffer.clone(), + buffer_position, + input, + push_to_lsp_host_history, + cx, + ) + }); + Some(cx.spawn(|editor, mut cx| async move { + if let Some(transaction) = on_type_formatting.await? { + if push_to_client_history { + buffer.update(&mut cx, |buffer, _| { + buffer.push_transaction(transaction, Instant::now()); + }); + } + editor.update(&mut cx, |editor, cx| { + editor.refresh_document_highlights(cx); + })?; + } + Ok(()) + })) + } fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { if self.pending_rename.is_some() { @@ -8134,15 +8135,14 @@ impl Editor { } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { - todo!() - // self.end_selection(cx); - // if let Some(tx_id) = self - // .buffer - // .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) - // { - // self.selection_history - // .insert_transaction(tx_id, self.selections.disjoint_anchors()); - // } + self.end_selection(cx); + if let Some(tx_id) = self + .buffer + .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) + { + self.selection_history + .insert_transaction(tx_id, self.selections.disjoint_anchors()); + } } fn end_transaction_at( @@ -8150,22 +8150,21 @@ impl Editor { now: Instant, cx: &mut ViewContext, ) -> Option { - todo!() - // if let Some(tx_id) = self - // .buffer - // .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) - // { - // if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { - // *end_selections = Some(self.selections.disjoint_anchors()); - // } else { - // error!("unexpectedly ended a transaction that wasn't started by this editor"); - // } - - // cx.emit(Event::Edited); - // Some(tx_id) - // } else { - // None - // } + if let Some(tx_id) = self + .buffer + .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) + { + if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { + *end_selections = Some(self.selections.disjoint_anchors()); + } else { + log::error!("unexpectedly ended a transaction that wasn't started by this editor"); + } + + cx.emit(Event::Edited); + Some(tx_id) + } else { + None + } } // pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { @@ -8688,17 +8687,17 @@ impl Editor { // results // } - // pub fn highlight_text( - // &mut self, - // ranges: Vec>, - // style: HighlightStyle, - // cx: &mut ViewContext, - // ) { - // self.display_map.update(cx, |map, _| { - // map.highlight_text(TypeId::of::(), ranges, style) - // }); - // cx.notify(); - // } + pub fn highlight_text( + &mut self, + ranges: Vec>, + style: HighlightStyle, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |map, _| { + map.highlight_text(TypeId::of::(), ranges, style) + }); + cx.notify(); + } // pub fn highlight_inlays( // &mut self, @@ -8712,12 +8711,12 @@ 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 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_highlights(&mut self, cx: &mut ViewContext) { let cleared = self @@ -8934,43 +8933,43 @@ impl Editor { // .detach_and_log_err(cx); // } - // fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { - // let snapshot = self.buffer.read(cx).read(cx); - // let (_, ranges) = self.text_highlights::(cx)?; - // Some( - // ranges - // .iter() - // .map(move |range| { - // range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) - // }) - // .collect(), - // ) - // } + fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { + let snapshot = self.buffer.read(cx).read(cx); + let (_, ranges) = self.text_highlights::(cx)?; + Some( + ranges + .iter() + .map(move |range| { + range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) + }) + .collect(), + ) + } - // fn selection_replacement_ranges( - // &self, - // range: Range, - // cx: &AppContext, - // ) -> Vec> { - // let selections = self.selections.all::(cx); - // let newest_selection = selections - // .iter() - // .max_by_key(|selection| selection.id) - // .unwrap(); - // let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; - // let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; - // let snapshot = self.buffer.read(cx).read(cx); - // selections - // .into_iter() - // .map(|mut selection| { - // selection.start.0 = - // (selection.start.0 as isize).saturating_add(start_delta) as usize; - // selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; - // snapshot.clip_offset_utf16(selection.start, Bias::Left) - // ..snapshot.clip_offset_utf16(selection.end, Bias::Right) - // }) - // .collect() - // } + fn selection_replacement_ranges( + &self, + range: Range, + cx: &AppContext, + ) -> Vec> { + let selections = self.selections.all::(cx); + let newest_selection = selections + .iter() + .max_by_key(|selection| selection.id) + .unwrap(); + let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; + let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; + let snapshot = self.buffer.read(cx).read(cx); + selections + .into_iter() + .map(|mut selection| { + selection.start.0 = + (selection.start.0 as isize).saturating_add(start_delta) as usize; + selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; + snapshot.clip_offset_utf16(selection.start, Bias::Left) + ..snapshot.clip_offset_utf16(selection.end, Bias::Right) + }) + .collect() + } fn report_copilot_event( &self, @@ -9485,214 +9484,229 @@ impl Render for Editor { // false // } -// -// fn text_for_range(&self, range_utf16: Range, cx: &AppContext) -> Option { -// Some( -// self.buffer -// .read(cx) -// .read(cx) -// .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) -// .collect(), -// ) -// } -// fn selected_text_range(&self, cx: &AppContext) -> Option> { -// // Prevent the IME menu from appearing when holding down an alphabetic key -// // while input is disabled. -// if !self.input_enabled { -// return None; -// } +impl InputHandler for Editor { + fn text_for_range( + &self, + range_utf16: Range, + cx: &mut ViewContext, + ) -> Option { + Some( + self.buffer + .read(cx) + .read(cx) + .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) + .collect(), + ) + } + + fn selected_text_range(&self, cx: &mut ViewContext) -> Option> { + // Prevent the IME menu from appearing when holding down an alphabetic key + // while input is disabled. + if !self.input_enabled { + return None; + } -// let range = self.selections.newest::(cx).range(); -// Some(range.start.0..range.end.0) -// } + let range = self.selections.newest::(cx).range(); + Some(range.start.0..range.end.0) + } -// fn marked_text_range(&self, cx: &AppContext) -> Option> { -// let snapshot = self.buffer.read(cx).read(cx); -// let range = self.text_highlights::(cx)?.1.get(0)?; -// Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) -// } + fn marked_text_range(&self, cx: &mut ViewContext) -> Option> { + let snapshot = self.buffer.read(cx).read(cx); + let range = self.text_highlights::(cx)?.1.get(0)?; + Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) + } -// fn unmark_text(&mut self, cx: &mut ViewContext) { -// self.clear_highlights::(cx); -// self.ime_transaction.take(); -// } + fn unmark_text(&mut self, cx: &mut ViewContext) { + self.clear_highlights::(cx); + self.ime_transaction.take(); + } -// fn replace_text_in_range( -// &mut self, -// range_utf16: Option>, -// text: &str, -// cx: &mut ViewContext, -// ) { -// if !self.input_enabled { -// cx.emit(Event::InputIgnored { text: text.into() }); -// return; -// } + fn replace_text_in_range( + &mut self, + range_utf16: Option>, + text: &str, + cx: &mut ViewContext, + ) { + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } -// self.transact(cx, |this, cx| { -// let new_selected_ranges = if let Some(range_utf16) = range_utf16 { -// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); -// Some(this.selection_replacement_ranges(range_utf16, cx)) -// } else { -// this.marked_text_ranges(cx) -// }; + self.transact(cx, |this, cx| { + let new_selected_ranges = if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + this.marked_text_ranges(cx) + }; -// let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { -// let newest_selection_id = this.selections.newest_anchor().id; -// this.selections -// .all::(cx) -// .iter() -// .zip(ranges_to_replace.iter()) -// .find_map(|(selection, range)| { -// if selection.id == newest_selection_id { -// Some( -// (range.start.0 as isize - selection.head().0 as isize) -// ..(range.end.0 as isize - selection.head().0 as isize), -// ) -// } else { -// None -// } -// }) -// }); + let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); -// if let Some(new_selected_ranges) = new_selected_ranges { -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } + if let Some(new_selected_ranges) = new_selected_ranges { + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } -// this.handle_input(text, cx); -// }); + this.handle_input(text, cx); + }); -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, cx); + }); + } -// self.unmark_text(cx); -// } + self.unmark_text(cx); + } -// fn replace_and_mark_text_in_range( -// &mut self, -// range_utf16: Option>, -// text: &str, -// new_selected_range_utf16: Option>, -// cx: &mut ViewContext, -// ) { -// if !self.input_enabled { -// cx.emit(Event::InputIgnored { text: text.into() }); -// return; -// } + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + text: &str, + new_selected_range_utf16: Option>, + cx: &mut ViewContext, + ) { + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } -// let transaction = self.transact(cx, |this, cx| { -// let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { -// let snapshot = this.buffer.read(cx).read(cx); -// if let Some(relative_range_utf16) = range_utf16.as_ref() { -// for marked_range in &mut marked_ranges { -// marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; -// marked_range.start.0 += relative_range_utf16.start; -// marked_range.start = -// snapshot.clip_offset_utf16(marked_range.start, Bias::Left); -// marked_range.end = -// snapshot.clip_offset_utf16(marked_range.end, Bias::Right); -// } -// } -// Some(marked_ranges) -// } else if let Some(range_utf16) = range_utf16 { -// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); -// Some(this.selection_replacement_ranges(range_utf16, cx)) -// } else { -// None -// }; + let transaction = self.transact(cx, |this, cx| { + let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { + let snapshot = this.buffer.read(cx).read(cx); + if let Some(relative_range_utf16) = range_utf16.as_ref() { + for marked_range in &mut marked_ranges { + marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; + marked_range.start.0 += relative_range_utf16.start; + marked_range.start = + snapshot.clip_offset_utf16(marked_range.start, Bias::Left); + marked_range.end = + snapshot.clip_offset_utf16(marked_range.end, Bias::Right); + } + } + Some(marked_ranges) + } else if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + None + }; -// let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { -// let newest_selection_id = this.selections.newest_anchor().id; -// this.selections -// .all::(cx) -// .iter() -// .zip(ranges_to_replace.iter()) -// .find_map(|(selection, range)| { -// if selection.id == newest_selection_id { -// Some( -// (range.start.0 as isize - selection.head().0 as isize) -// ..(range.end.0 as isize - selection.head().0 as isize), -// ) -// } else { -// None -// } -// }) -// }); + let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); -// if let Some(ranges) = ranges_to_replace { -// this.change_selections(None, cx, |s| s.select_ranges(ranges)); -// } + if let Some(ranges) = ranges_to_replace { + this.change_selections(None, cx, |s| s.select_ranges(ranges)); + } -// let marked_ranges = { -// let snapshot = this.buffer.read(cx).read(cx); -// this.selections -// .disjoint_anchors() -// .iter() -// .map(|selection| { -// selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) -// }) -// .collect::>() -// }; + let marked_ranges = { + let snapshot = this.buffer.read(cx).read(cx); + this.selections + .disjoint_anchors() + .iter() + .map(|selection| { + selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) + }) + .collect::>() + }; -// if text.is_empty() { -// this.unmark_text(cx); -// } else { -// this.highlight_text::( -// marked_ranges.clone(), -// this.style(cx).composition_mark, -// cx, -// ); -// } + if text.is_empty() { + this.unmark_text(cx); + } else { + this.highlight_text::( + marked_ranges.clone(), + HighlightStyle::default(), // todo!() this.style(cx).composition_mark, + cx, + ); + } -// this.handle_input(text, cx); - -// if let Some(new_selected_range) = new_selected_range_utf16 { -// let snapshot = this.buffer.read(cx).read(cx); -// let new_selected_ranges = marked_ranges -// .into_iter() -// .map(|marked_range| { -// let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; -// let new_start = OffsetUtf16(new_selected_range.start + insertion_start); -// let new_end = OffsetUtf16(new_selected_range.end + insertion_start); -// snapshot.clip_offset_utf16(new_start, Bias::Left) -// ..snapshot.clip_offset_utf16(new_end, Bias::Right) -// }) -// .collect::>(); - -// drop(snapshot); -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } -// }); + this.handle_input(text, cx); -// self.ime_transaction = self.ime_transaction.or(transaction); -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + if let Some(new_selected_range) = new_selected_range_utf16 { + let snapshot = this.buffer.read(cx).read(cx); + let new_selected_ranges = marked_ranges + .into_iter() + .map(|marked_range| { + let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; + let new_start = OffsetUtf16(new_selected_range.start + insertion_start); + let new_end = OffsetUtf16(new_selected_range.end + insertion_start); + snapshot.clip_offset_utf16(new_start, Bias::Left) + ..snapshot.clip_offset_utf16(new_end, Bias::Right) + }) + .collect::>(); -// if self.text_highlights::(cx).is_none() { -// self.ime_transaction.take(); -// } -// } -// } + drop(snapshot); + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } + }); + + self.ime_transaction = self.ime_transaction.or(transaction); + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, cx); + }); + } + + if self.text_highlights::(cx).is_none() { + self.ime_transaction.take(); + } + } + + fn bounds_for_range( + &self, + range_utf16: Range, + cx: &mut ViewContext, + ) -> Option> { + // todo!() + // See how we did it before: `rect_for_range` + None + } +} // fn build_style( // settings: &ThemeSettings, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 5af5d97e03ceed35569acbb950e0f75d3fceb7c6..4e16ff450413d7f143820a8e8797e9f69ef7b51f 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2623,6 +2623,10 @@ impl Element for EditorElement { } }); + if editor.focus_handle.is_focused(cx) { + cx.handle_text_input(); + } + cx.with_content_mask(ContentMask { bounds }, |cx| { let gutter_bounds = Bounds { origin: bounds.origin, diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 63d2143a6726773be7508c77c72bdd6b1052fe3d..42d34e0c202e9ceaddbabeb85e6f6fac23502d39 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -154,7 +154,7 @@ type ReleaseListener = Box; // } pub struct AppContext { - this: Weak, + pub(crate) this: Weak, pub(crate) platform: Rc, app_metadata: AppMetadata, text_system: Arc, diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 348da17356dd3f48d1a507cfcb0c8d19a4413d2b..e59b196a91ff252da9a41179912b1e7e718ba0c1 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -24,6 +24,7 @@ mod text_system; mod util; mod view; mod window; +mod window_input_handler; mod private { /// A mechanism for restricting implementations of a trait to only those in GPUI. @@ -64,6 +65,7 @@ pub use text_system::*; pub use util::arc_cow::ArcCow; pub use view::*; pub use window::*; +pub use window_input_handler::*; use derive_more::{Deref, DerefMut}; use std::{ diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5b0349c7d48ff9a84478b624205b65c9c1be2c75..50c7b772f882f07aa99e668676f1e7b5ddc8688a 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2,13 +2,14 @@ use crate::{ px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, - GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, - KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, - Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, InputHandler, IsZero, KeyListener, + KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, + PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, + Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowBounds, WindowInputHandler, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -191,6 +192,7 @@ pub struct Window { default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, + requested_input_handler: Option>, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, @@ -253,7 +255,7 @@ impl Window { handle .update(&mut cx, |_, cx| cx.dispatch_event(event)) .log_err() - .unwrap_or(true) + .unwrap_or(false) }) }); @@ -285,6 +287,7 @@ impl Window { default_prevented: true, mouse_position, requested_cursor_style: None, + requested_input_handler: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), @@ -1008,6 +1011,9 @@ impl<'a> WindowContext<'a> { .take() .unwrap_or(CursorStyle::Arrow); self.platform.set_cursor_style(cursor_style); + if let Some(handler) = self.window.requested_input_handler.take() { + self.window.platform_window.set_input_handler(handler); + } self.window.dirty = false; } @@ -1152,6 +1158,7 @@ impl<'a> WindowContext<'a> { .insert(any_mouse_event.type_id(), handlers); } } else if let Some(any_key_event) = event.keyboard_event() { + let mut did_handle_action = false; let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack); let key_event_type = any_key_event.type_id(); let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new(); @@ -1172,6 +1179,7 @@ impl<'a> WindowContext<'a> { self.dispatch_action(action, &key_dispatch_stack[..ix]); } if !self.app.propagate_event { + did_handle_action = true; break; } } @@ -1200,6 +1208,7 @@ impl<'a> WindowContext<'a> { } if !self.app.propagate_event { + did_handle_action = true; break; } } @@ -1213,6 +1222,7 @@ impl<'a> WindowContext<'a> { drop(context_stack); self.window.key_dispatch_stack = key_dispatch_stack; + return did_handle_action; } true @@ -1995,6 +2005,19 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } +impl ViewContext<'_, V> +where + V: InputHandler + 'static, +{ + pub fn handle_text_input(&mut self) { + self.window.requested_input_handler = Some(Box::new(WindowInputHandler { + cx: self.app.this.clone(), + window: self.window_handle(), + handler: self.view().downgrade(), + })); + } +} + impl ViewContext<'_, V> where V: EventEmitter, diff --git a/crates/gpui2/src/window_input_handler.rs b/crates/gpui2/src/window_input_handler.rs new file mode 100644 index 0000000000000000000000000000000000000000..caae5838ce46171a1584b272d7e916d100e13881 --- /dev/null +++ b/crates/gpui2/src/window_input_handler.rs @@ -0,0 +1,89 @@ +use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView}; +use std::{ops::Range, rc::Weak}; + +pub struct WindowInputHandler +where + V: InputHandler, +{ + pub cx: Weak, + pub window: AnyWindowHandle, + pub handler: WeakView, +} + +impl PlatformInputHandler for WindowInputHandler { + fn selected_text_range(&self) -> Option> { + self.update(|view, cx| view.selected_text_range(cx)) + .flatten() + } + + fn marked_text_range(&self) -> Option> { + self.update(|view, cx| view.marked_text_range(cx)).flatten() + } + + fn text_for_range(&self, range_utf16: std::ops::Range) -> Option { + self.update(|view, cx| view.text_for_range(range_utf16, cx)) + .flatten() + } + + fn replace_text_in_range( + &mut self, + replacement_range: Option>, + text: &str, + ) { + self.update(|view, cx| view.replace_text_in_range(replacement_range, text, cx)); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + new_selected_range: Option>, + ) { + self.update(|view, cx| { + view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx) + }); + } + + fn unmark_text(&mut self) { + self.update(|view, cx| view.unmark_text(cx)); + } + + fn bounds_for_range(&self, range_utf16: std::ops::Range) -> Option> { + self.update(|view, cx| view.bounds_for_range(range_utf16, cx)) + .flatten() + } +} + +impl WindowInputHandler { + fn update(&self, f: impl FnOnce(&mut V, &mut ViewContext) -> T) -> Option { + let cx = self.cx.upgrade()?; + let mut cx = cx.borrow_mut(); + cx.update_window(self.window, |_, cx| self.handler.update(cx, f).ok()) + .ok()? + } +} + +pub trait InputHandler: Sized { + fn text_for_range(&self, range: Range, cx: &mut ViewContext) -> Option; + fn selected_text_range(&self, cx: &mut ViewContext) -> Option>; + fn marked_text_range(&self, cx: &mut ViewContext) -> Option>; + fn unmark_text(&mut self, cx: &mut ViewContext); + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut ViewContext, + ); + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + cx: &mut ViewContext, + ); + fn bounds_for_range( + &self, + range_utf16: std::ops::Range, + cx: &mut ViewContext, + ) -> Option>; +} diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 90204f60386e02cb08eb171d381b9e97b6fbea7f..b51b0186efd4ab4bccd8822f407141a05eeb2b10 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2694,7 +2694,7 @@ impl Workspace { .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); if is_edited != self.window_edited { self.window_edited = is_edited; - todo!() + // todo!() // cx.set_window_edited(self.window_edited) } }