diff --git a/Cargo.lock b/Cargo.lock index 7d17b48cc7..19b1ca6c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9005,6 +9005,7 @@ dependencies = [ "fs2", "gpui2", "indexmap 1.9.3", + "itertools 0.11.0", "parking_lot 0.11.2", "refineable", "schemars", diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index eaf91b089b..a37442be57 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -36,10 +36,10 @@ pub use element::{ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, div, px, 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, + actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context, + 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, }; @@ -592,7 +593,6 @@ pub struct EditorStyle { pub background: Hsla, pub local_player: PlayerColor, pub text: TextStyle, - pub line_height_scalar: f32, pub scrollbar_width: Pixels, pub syntax: Arc, pub diagnostic_style: DiagnosticStyle, @@ -1794,14 +1794,11 @@ impl InlayHintRefreshReason { } impl Editor { - // pub fn single_line( - // field_editor_style: Option>, - // cx: &mut ViewContext, - // ) -> Self { - // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); - // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); - // Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx) - // } + pub fn single_line(cx: &mut ViewContext) -> Self { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new())); + let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::SingleLine, buffer, None, cx) + } // pub fn multi_line( // field_editor_style: Option>, @@ -2772,197 +2769,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 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 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| { @@ -3262,22 +3259,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. @@ -3308,37 +3305,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); + /// 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; - // } - // } + 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( @@ -3540,51 +3537,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)?; + 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(); + // 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 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() { @@ -8137,15 +8134,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( @@ -8153,22 +8149,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"); - // } + 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 - // } + cx.emit(Event::Edited); + Some(tx_id) + } else { + None + } } // pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { @@ -8366,9 +8361,9 @@ impl Editor { // .max_point() // } - // pub fn text(&self, cx: &AppContext) -> String { - // self.buffer.read(cx).read(cx).text() - // } + pub fn text(&self, cx: &AppContext) -> String { + self.buffer.read(cx).read(cx).text() + } // pub fn set_text(&mut self, text: impl Into>, cx: &mut ViewContext) { // self.transact(cx, |this, cx| { @@ -8691,17 +8686,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, @@ -8715,12 +8710,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 @@ -8937,43 +8932,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, @@ -9188,6 +9183,10 @@ impl Editor { // }); // supports // } + + fn focus(&self, cx: &mut WindowContext) { + cx.focus(&self.focus_handle) + } } pub trait CollaborationHub { @@ -9371,14 +9370,13 @@ impl Render for Editor { font_size: settings.buffer_font_size.into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: Default::default(), + line_height: relative(settings.buffer_line_height.value()), underline: None, }; EditorElement::new(EditorStyle { background: cx.theme().colors().editor_background, local_player: cx.theme().players().local(), text: text_style, - line_height_scalar: settings.buffer_line_height.value(), scrollbar_width: px(12.), syntax: cx.theme().syntax().clone(), diagnostic_style: cx.theme().diagnostic_style(), @@ -9485,214 +9483,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(), + ) + } -// let range = self.selections.newest::(cx).range(); -// Some(range.start.0..range.end.0) -// } + 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; + } -// 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) -// } + let range = self.selections.newest::(cx).range(); + Some(range.start.0..range.end.0) + } -// fn unmark_text(&mut self, cx: &mut ViewContext) { -// self.clear_highlights::(cx); -// self.ime_transaction.take(); -// } + 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 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 unmark_text(&mut self, cx: &mut ViewContext) { + self.clear_highlights::(cx); + self.ime_transaction.take(); + } -// 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) -// }; + 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; + } -// 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 -// } -// }) -// }); + 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) + }; -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + 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 + } + }) + }); -// if let Some(new_selected_ranges) = new_selected_ranges { -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); -// this.handle_input(text, cx); -// }); + if let Some(new_selected_ranges) = new_selected_ranges { + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + this.handle_input(text, cx); + }); -// self.unmark_text(cx); -// } + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, 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; -// } + self.unmark_text(cx); + } -// 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 -// }; + 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 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 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 + }; -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + 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 + } + }) + }); -// if let Some(ranges) = ranges_to_replace { -// this.change_selections(None, cx, |s| s.select_ranges(ranges)); -// } + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); -// 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 let Some(ranges) = ranges_to_replace { + this.change_selections(None, cx, |s| s.select_ranges(ranges)); + } -// if text.is_empty() { -// this.unmark_text(cx); -// } else { -// this.highlight_text::( -// marked_ranges.clone(), -// this.style(cx).composition_mark, -// cx, -// ); -// } + 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::>() + }; -// this.handle_input(text, 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, + ); + } -// 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::>(); + this.handle_input(text, cx); -// drop(snapshot); -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } -// }); + 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::>(); -// 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); -// }); -// } + drop(snapshot); + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } + }); -// if self.text_highlights::(cx).is_none() { -// self.ime_transaction.take(); -// } -// } -// } + 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 09f9ef1a59..4e16ff4504 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -8,10 +8,11 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, BorrowWindow, - Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, Edges, Element, ElementId, - Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, ScrollWheelEvent, ShapedGlyph, - Size, StatefulInteraction, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, + black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, + BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, + Edges, Element, ElementId, Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, + ScrollWheelEvent, ShapedGlyph, Size, StatefulInteraction, Style, TextRun, TextStyle, + TextSystem, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -1594,7 +1595,7 @@ impl EditorElement { &mut self, editor: &mut Editor, cx: &mut ViewContext<'_, Editor>, - bounds: Bounds, + mut bounds: Bounds, ) -> LayoutState { // let mut size = constraint.max; // if size.x.is_infinite() { @@ -1605,7 +1606,7 @@ impl EditorElement { let style = self.style.clone(); let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); let font_size = style.text.font_size.to_pixels(cx.rem_size()); - let line_height = (font_size * style.line_height_scalar).round(); + let line_height = style.text.line_height_in_pixels(cx.rem_size()); let em_width = cx .text_system() .typographic_bounds(font_id, font_size, 'm') @@ -1672,8 +1673,7 @@ impl EditorElement { // .min(line_height * max_lines as f32), // ) } else if let EditorMode::SingleLine = editor_mode { - todo!() - // size.set_y(line_height.max(constraint.min_along(Axis::Vertical))) + bounds.size.height = line_height.min(bounds.size.height); } // todo!() // else if size.y.is_infinite() { @@ -2593,7 +2593,11 @@ impl Element for EditorElement { let rem_size = cx.rem_size(); let mut style = Style::default(); style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); + style.size.height = match editor.mode { + EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(), + EditorMode::AutoHeight { .. } => todo!(), + EditorMode::Full => relative(1.).into(), + }; cx.request_layout(&style, None) } @@ -2619,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/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 764602c986..13d283ecff 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -1,12 +1,10 @@ -use gpui::{div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext}; -use serde::Deserialize; +use gpui::{actions, div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext}; use workspace::ModalRegistry; -// actions!(go_to_line, [Toggle]); -#[derive(Clone, Default, PartialEq, Deserialize)] -struct Toggle; +actions!(Toggle); pub fn init(cx: &mut AppContext) { + cx.register_action_type::(); cx.global_mut::() .register_modal(Toggle, |_, cx| { // if let Some(editor) = workspace diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 0267f47236..f2ac3a91cf 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -153,7 +153,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/element.rs b/crates/gpui2/src/element.rs index 2a0f557272..8fdc17de07 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -134,7 +134,10 @@ where .layout(state, frame_state.as_mut().unwrap(), cx); } } - _ => panic!("must call initialize before layout"), + ElementRenderPhase::Start => panic!("must call initialize before layout"), + ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => { + panic!("element rendered twice") + } }; self.phase = ElementRenderPhase::LayoutRequested { diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 4441c9565f..8f3dc6c314 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. @@ -65,6 +66,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/interactive.rs b/crates/gpui2/src/interactive.rs index 0725a10aca..84beb56ef8 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -397,9 +397,10 @@ pub trait ElementInteraction: 'static { None }), )); - let result = stateful.stateless.initialize(cx, f); - stateful.key_listeners.pop(); - result + + cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| { + cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f) + }) }) } else { let stateless = self.as_stateless_mut(); diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 551a87624c..5de173c2d4 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -189,6 +189,10 @@ impl TextStyle { } } + pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels { + self.line_height.to_pixels(self.font_size, rem_size) + } + pub fn to_run(&self, len: usize) -> TextRun { TextRun { len, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5b0349c7d4..50c7b772f8 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 0000000000..caae5838ce --- /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/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index ec1f02a95d..9f279864ee 100644 --- a/crates/settings2/src/keymap_file.rs +++ b/crates/settings2/src/keymap_file.rs @@ -1,7 +1,7 @@ use crate::{settings_store::parse_json_with_comments, SettingsAssets}; use anyhow::{anyhow, Context, Result}; use collections::BTreeMap; -use gpui::{AppContext, KeyBinding, SharedString}; +use gpui::{actions, Action, AppContext, KeyBinding, SharedString}; use schemars::{ gen::{SchemaGenerator, SchemaSettings}, schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation}, @@ -137,8 +137,10 @@ impl KeymapFile { } } +actions!(NoAction); + fn no_action() -> Box { - todo!() + NoAction.boxed_clone() } #[cfg(test)] diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index f59208ccb8..47807def25 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -38,6 +38,7 @@ pub enum ComponentStory { Palette, Panel, ProjectPanel, + Players, RecentProjects, Scroll, Tab, @@ -79,6 +80,7 @@ impl ComponentStory { Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(), Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(), Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(), + Self::Players => cx.build_view(|_| theme2::PlayerStory).into(), Self::Panel => cx.build_view(|cx| ui::PanelStory).into(), Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(), Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(), diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index d57c22ede7..45ba4587ba 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" publish = false [features] +default = ["stories"] +stories = ["dep:itertools"] test-support = [ "gpui/test-support", "fs/test-support", @@ -30,6 +32,7 @@ settings = { package = "settings2", path = "../settings2" } toml.workspace = true uuid.workspace = true util = { path = "../util" } +itertools = { version = "0.11.0", optional = true } [dev-dependencies] gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index bb7d44e5d2..8e3db63537 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use gpui::Hsla; use refineable::Refineable; -use crate::SyntaxTheme; +use crate::{PlayerColors, SyntaxTheme}; #[derive(Clone)] pub struct SystemColors { @@ -13,33 +13,6 @@ pub struct SystemColors { pub mac_os_traffic_light_green: Hsla, } -#[derive(Debug, Clone, Copy)] -pub struct PlayerColor { - pub cursor: Hsla, - pub background: Hsla, - pub selection: Hsla, -} - -#[derive(Clone)] -pub struct PlayerColors(pub Vec); - -impl PlayerColors { - pub fn local(&self) -> PlayerColor { - // todo!("use a valid color"); - *self.0.first().unwrap() - } - - pub fn absent(&self) -> PlayerColor { - // todo!("use a valid color"); - *self.0.last().unwrap() - } - - pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor { - let len = self.0.len() - 1; - self.0[(participant_index as usize % len) + 1] - } -} - #[derive(Refineable, Clone, Debug)] #[refineable(debug)] pub struct StatusColors { @@ -71,71 +44,213 @@ pub struct GitStatusColors { #[refineable(debug, deserialize)] pub struct ThemeColors { pub border: Hsla, + /// Border color. Used for deemphasized borders, like a visual divider between two sections pub border_variant: Hsla, + /// Border color. Used for focused elements, like keyboard focused list item. pub border_focused: Hsla, + /// Border color. Used for selected elements, like an active search filter or selected checkbox. pub border_selected: Hsla, + /// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change. pub border_transparent: Hsla, + /// Border color. Used for disabled elements, like a disabled input or button. pub border_disabled: Hsla, + /// Border color. Used for elevated surfaces, like a context menu, popup, or dialog. pub elevated_surface_background: Hsla, + /// Background Color. Used for grounded surfaces like a panel or tab. pub surface_background: Hsla, + /// Background Color. Used for the app background and blank panels or windows. pub background: Hsla, + /// Background Color. Used for the background of an element that should have a different background than the surface it's on. + /// + /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... + /// + /// For an element that should have the same background as the surface it's on, use `ghost_element_background`. pub element_background: Hsla, + /// Background Color. Used for the hover state of an element that should have a different background than the surface it's on. + /// + /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. pub element_hover: Hsla, + /// Background Color. Used for the active state of an element that should have a different background than the surface it's on. + /// + /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd. pub element_active: Hsla, + /// Background Color. Used for the selected state of an element that should have a different background than the surface it's on. + /// + /// Selected states are triggered by the element being selected (or "activated") by the user. + /// + /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub element_selected: Hsla, + /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on. + /// + /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. pub element_disabled: Hsla, - pub element_placeholder: Hsla, - pub element_drop_target: Hsla, + /// Background Color. Used for the area that shows where a dragged element will be dropped. + pub drop_target_background: Hsla, + /// Border Color. Used to show the area that shows where a dragged element will be dropped. + // pub drop_target_border: Hsla, + /// Used for the background of a ghost element that should have the same background as the surface it's on. + /// + /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... + /// + /// For an element that should have a different background than the surface it's on, use `element_background`. pub ghost_element_background: Hsla, + /// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on. + /// + /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. pub ghost_element_hover: Hsla, + /// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on. + /// + /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd. pub ghost_element_active: Hsla, + /// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on. + /// + /// Selected states are triggered by the element being selected (or "activated") by the user. + /// + /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub ghost_element_selected: Hsla, + /// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on. + /// + /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. pub ghost_element_disabled: Hsla, + /// Text Color. Default text color used for most text. pub text: Hsla, + /// Text Color. Color of muted or deemphasized text. It is a subdued version of the standard text color. pub text_muted: Hsla, + /// Text Color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data. pub text_placeholder: Hsla, + /// Text Color. Color used for text denoting disabled elements. Typically, the color is faded or grayed out to emphasize the disabled state. pub text_disabled: Hsla, + /// Text Color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search. pub text_accent: Hsla, + /// Fill Color. Used for the default fill color of an icon. pub icon: Hsla, + /// Fill Color. Used for the muted or deemphasized fill color of an icon. + /// + /// This might be used to show an icon in an inactive pane, or to demphasize a series of icons to give them less visual weight. pub icon_muted: Hsla, + /// Fill Color. Used for the disabled fill color of an icon. + /// + /// Disabled states are shown when a user cannot interact with an element, like a icon button. pub icon_disabled: Hsla, + /// Fill Color. Used for the placeholder fill color of an icon. + /// + /// This might be used to show an icon in an input that disappears when the user enters text. pub icon_placeholder: Hsla, + /// Fill Color. Used for the accent fill color of an icon. + /// + /// This might be used to show when a toggleable icon button is selected. pub icon_accent: Hsla, + + // === + // UI Elements + // === pub status_bar_background: Hsla, pub title_bar_background: Hsla, pub toolbar_background: Hsla, pub tab_bar_background: Hsla, pub tab_inactive_background: Hsla, pub tab_active_background: Hsla, + // pub panel_background: Hsla, + // pub pane_focused_border: Hsla, + // /// The color of the scrollbar thumb. + // pub scrollbar_thumb_background: Hsla, + // /// The color of the scrollbar thumb when hovered over. + // pub scrollbar_thumb_hover_background: Hsla, + // /// The border color of the scrollbar thumb. + // pub scrollbar_thumb_border: Hsla, + // /// The background color of the scrollbar track. + // pub scrollbar_track_background: Hsla, + // /// The border color of the scrollbar track. + // pub scrollbar_track_border: Hsla, + // /// The opacity of the scrollbar status marks, like diagnostic states and git status.. + // pub scrollbar_status_opacity: Hsla, + + // === + // Editor + // === pub editor_background: Hsla, + // pub editor_inactive_background: Hsla, pub editor_gutter_background: Hsla, pub editor_subheader_background: Hsla, pub editor_active_line_background: Hsla, pub editor_highlighted_line_background: Hsla, + /// Text Color. Used for the text of the line number in the editor gutter. pub editor_line_number: Hsla, + /// Text Color. Used for the text of the line number in the editor gutter when the line is highlighted. pub editor_active_line_number: Hsla, + /// Text Color. Used to mark invisible characters in the editor. + /// + /// Example: spaces, tabs, carriage returns, etc. pub editor_invisible: Hsla, pub editor_wrap_guide: Hsla, pub editor_active_wrap_guide: Hsla, pub editor_document_highlight_read_background: Hsla, pub editor_document_highlight_write_background: Hsla, + + // === + // Terminal + // === + /// Terminal Background Color pub terminal_background: Hsla, + /// Bright Black Color for ANSI Terminal pub terminal_ansi_bright_black: Hsla, + /// Bright Red Color for ANSI Terminal pub terminal_ansi_bright_red: Hsla, + /// Bright Green Color for ANSI Terminal pub terminal_ansi_bright_green: Hsla, + /// Bright Yellow Color for ANSI Terminal pub terminal_ansi_bright_yellow: Hsla, + /// Bright Blue Color for ANSI Terminal pub terminal_ansi_bright_blue: Hsla, + /// Bright Magenta Color for ANSI Terminal pub terminal_ansi_bright_magenta: Hsla, + /// Bright Cyan Color for ANSI Terminal pub terminal_ansi_bright_cyan: Hsla, + /// Bright White Color for ANSI Terminal pub terminal_ansi_bright_white: Hsla, + /// Black Color for ANSI Terminal pub terminal_ansi_black: Hsla, + /// Red Color for ANSI Terminal pub terminal_ansi_red: Hsla, + /// Green Color for ANSI Terminal pub terminal_ansi_green: Hsla, + /// Yellow Color for ANSI Terminal pub terminal_ansi_yellow: Hsla, + /// Blue Color for ANSI Terminal pub terminal_ansi_blue: Hsla, + /// Magenta Color for ANSI Terminal pub terminal_ansi_magenta: Hsla, + /// Cyan Color for ANSI Terminal pub terminal_ansi_cyan: Hsla, + /// White Color for ANSI Terminal pub terminal_ansi_white: Hsla, + // new colors + + // === + // Elevation + // === + // elevation_0_shadow + // elevation_0_shadow_color + // elevation_1_shadow + // elevation_1_shadow_color + // elevation_2_shadow + // elevation_2_shadow_color + // elevation_3_shadow + // elevation_3_shadow_color + // elevation_4_shadow + // elevation_4_shadow_color + // elevation_5_shadow + // elevation_5_shadow_color + + // === + // UI Text + // === + // pub headline: Hsla, + // pub paragraph: Hsla, + // pub link: Hsla, + // pub link_hover: Hsla, + // pub code_block_background: Hsla, + // pub code_block_border: Hsla, } #[derive(Refineable, Clone)] diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index c540fb40a9..3a626205f9 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -3,12 +3,106 @@ use std::num::ParseIntError; use gpui::{hsla, Hsla, Rgba}; use crate::{ - colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, + colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, - ColorScale, + ColorScale, PlayerColor, PlayerColors, }; +impl Default for PlayerColors { + fn default() -> Self { + Self(vec![ + PlayerColor { + cursor: blue().dark().step_9(), + background: blue().dark().step_5(), + selection: blue().dark().step_3(), + }, + PlayerColor { + cursor: orange().dark().step_9(), + background: orange().dark().step_5(), + selection: orange().dark().step_3(), + }, + PlayerColor { + cursor: pink().dark().step_9(), + background: pink().dark().step_5(), + selection: pink().dark().step_3(), + }, + PlayerColor { + cursor: lime().dark().step_9(), + background: lime().dark().step_5(), + selection: lime().dark().step_3(), + }, + PlayerColor { + cursor: purple().dark().step_9(), + background: purple().dark().step_5(), + selection: purple().dark().step_3(), + }, + PlayerColor { + cursor: amber().dark().step_9(), + background: amber().dark().step_5(), + selection: amber().dark().step_3(), + }, + PlayerColor { + cursor: jade().dark().step_9(), + background: jade().dark().step_5(), + selection: jade().dark().step_3(), + }, + PlayerColor { + cursor: red().dark().step_9(), + background: red().dark().step_5(), + selection: red().dark().step_3(), + }, + ]) + } +} + +impl PlayerColors { + pub fn default_light() -> Self { + Self(vec![ + PlayerColor { + cursor: blue().light().step_9(), + background: blue().light().step_4(), + selection: blue().light().step_3(), + }, + PlayerColor { + cursor: orange().light().step_9(), + background: orange().light().step_4(), + selection: orange().light().step_3(), + }, + PlayerColor { + cursor: pink().light().step_9(), + background: pink().light().step_4(), + selection: pink().light().step_3(), + }, + PlayerColor { + cursor: lime().light().step_9(), + background: lime().light().step_4(), + selection: lime().light().step_3(), + }, + PlayerColor { + cursor: purple().light().step_9(), + background: purple().light().step_4(), + selection: purple().light().step_3(), + }, + PlayerColor { + cursor: amber().light().step_9(), + background: amber().light().step_4(), + selection: amber().light().step_3(), + }, + PlayerColor { + cursor: jade().light().step_9(), + background: jade().light().step_4(), + selection: jade().light().step_3(), + }, + PlayerColor { + cursor: red().light().step_9(), + background: red().light().step_4(), + selection: red().light().step_3(), + }, + ]) + } +} + fn neutral() -> ColorScaleSet { slate() } @@ -27,17 +121,17 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: red().dark().step_11(), - created: grass().dark().step_11(), - deleted: red().dark().step_11(), - error: red().dark().step_11(), - hidden: neutral().dark().step_11(), - ignored: neutral().dark().step_11(), - info: blue().dark().step_11(), - modified: yellow().dark().step_11(), - renamed: blue().dark().step_11(), - success: grass().dark().step_11(), - warning: yellow().dark().step_11(), + conflict: red().dark().step_9(), + created: grass().dark().step_9(), + deleted: red().dark().step_9(), + error: red().dark().step_9(), + hidden: neutral().dark().step_9(), + ignored: neutral().dark().step_9(), + info: blue().dark().step_9(), + modified: yellow().dark().step_9(), + renamed: blue().dark().step_9(), + success: grass().dark().step_9(), + warning: yellow().dark().step_9(), } } } @@ -45,43 +139,16 @@ impl Default for StatusColors { impl Default for GitStatusColors { fn default() -> Self { Self { - conflict: orange().dark().step_11(), - created: grass().dark().step_11(), - deleted: red().dark().step_11(), - ignored: neutral().dark().step_11(), - modified: yellow().dark().step_11(), - renamed: blue().dark().step_11(), + conflict: orange().dark().step_9(), + created: grass().dark().step_9(), + deleted: red().dark().step_9(), + ignored: neutral().dark().step_9(), + modified: yellow().dark().step_9(), + renamed: blue().dark().step_9(), } } } -impl Default for PlayerColors { - fn default() -> Self { - Self(vec![ - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), - }, - ]) - } -} - impl SyntaxTheme { pub fn default_light() -> Self { Self { @@ -220,8 +287,7 @@ impl ThemeColors { element_active: neutral().light().step_5(), element_selected: neutral().light().step_5(), element_disabled: neutral().light_alpha().step_3(), - element_placeholder: neutral().light().step_11(), - element_drop_target: blue().light_alpha().step_2(), + drop_target_background: blue().light_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().light().step_4(), ghost_element_active: neutral().light().step_5(), @@ -293,8 +359,7 @@ impl ThemeColors { element_active: neutral().dark().step_5(), element_selected: neutral().dark().step_5(), element_disabled: neutral().dark_alpha().step_3(), - element_placeholder: neutral().dark().step_11(), - element_drop_target: blue().dark_alpha().step_2(), + drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().dark().step_4(), ghost_element_active: neutral().dark().step_5(), diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 3c9634c989..a0947e47c3 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, - default_color_scales, Appearance, SyntaxTheme, Theme, ThemeFamily, + colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, + default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily, }; fn zed_pro_daylight() -> Theme { @@ -15,7 +15,7 @@ fn zed_pro_daylight() -> Theme { colors: ThemeColors::default_light(), status: StatusColors::default(), git: GitStatusColors::default(), - player: PlayerColors::default(), + player: PlayerColors::default_light(), syntax: Arc::new(SyntaxTheme::default_light()), }, } diff --git a/crates/theme2/src/players.rs b/crates/theme2/src/players.rs new file mode 100644 index 0000000000..0e36ff5947 --- /dev/null +++ b/crates/theme2/src/players.rs @@ -0,0 +1,170 @@ +use gpui::Hsla; + +#[derive(Debug, Clone, Copy)] +pub struct PlayerColor { + pub cursor: Hsla, + pub background: Hsla, + pub selection: Hsla, +} + +/// A collection of colors that are used to color players in the editor. +/// +/// The first color is always the local player's color, usually a blue. +/// +/// The rest of the default colors crisscross back and forth on the +/// color wheel so that the colors are as distinct as possible. +#[derive(Clone)] +pub struct PlayerColors(pub Vec); + +impl PlayerColors { + pub fn local(&self) -> PlayerColor { + // todo!("use a valid color"); + *self.0.first().unwrap() + } + + pub fn absent(&self) -> PlayerColor { + // todo!("use a valid color"); + *self.0.last().unwrap() + } + + pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor { + let len = self.0.len() - 1; + self.0[(participant_index as usize % len) + 1] + } +} + +#[cfg(feature = "stories")] +pub use stories::*; + +#[cfg(feature = "stories")] +mod stories { + use super::*; + use crate::{ActiveTheme, Story}; + use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext}; + + pub struct PlayerStory; + + impl Render for PlayerStory { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + Story::container(cx).child( + div() + .flex() + .flex_col() + .gap_4() + .child(Story::title_for::<_, PlayerColors>(cx)) + .child(Story::label(cx, "Player Colors")) + .child( + div() + .flex() + .flex_col() + .gap_1() + .child( + div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.cursor) + }), + ), + ) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.background) + }), + )) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.selection) + }), + )), + ) + .child(Story::label(cx, "Avatar Rings")) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .my_1() + .rounded_full() + .border_2() + .border_color(player.cursor) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size_6() + .bg(gpui::red()), + ) + }), + )) + .child(Story::label(cx, "Player Backgrounds")) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .my_1() + .rounded_xl() + .flex() + .items_center() + .h_8() + .py_0p5() + .px_1p5() + .bg(player.background) + .child( + div().relative().neg_mx_1().rounded_full().z_index(3) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ).child( + div().relative().neg_mx_1().rounded_full().z_index(2) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ).child( + div().relative().neg_mx_1().rounded_full().z_index(1) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ) + }), + )) + .child(Story::label(cx, "Player Selections")) + .child(div().flex().flex_col().gap_px().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .flex() + .child( + div() + .flex() + .flex_none() + .rounded_sm() + .px_0p5() + .text_color(cx.theme().colors().text) + .bg(player.selection) + .child("The brown fox jumped over the lazy dog."), + ) + .child(div().flex_1()) + }), + )), + ) + } + } +} diff --git a/crates/theme2/src/story.rs b/crates/theme2/src/story.rs new file mode 100644 index 0000000000..8b3754b59e --- /dev/null +++ b/crates/theme2/src/story.rs @@ -0,0 +1,38 @@ +use gpui::{div, Component, Div, ParentElement, Styled, ViewContext}; + +use crate::ActiveTheme; + +pub struct Story {} + +impl Story { + pub fn container(cx: &mut ViewContext) -> Div { + div() + .size_full() + .flex() + .flex_col() + .pt_2() + .px_4() + .font("Zed Mono") + .bg(cx.theme().colors().background) + } + + pub fn title(cx: &mut ViewContext, title: &str) -> impl Component { + div() + .text_xl() + .text_color(cx.theme().colors().text) + .child(title.to_owned()) + } + + pub fn title_for(cx: &mut ViewContext) -> impl Component { + Self::title(cx, std::any::type_name::()) + } + + pub fn label(cx: &mut ViewContext, label: &str) -> impl Component { + div() + .mt_4() + .mb_2() + .text_xs() + .text_color(cx.theme().colors().text) + .child(label.to_owned()) + } +} diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 88db3c55f4..9019eba07a 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -1,6 +1,7 @@ mod colors; mod default_colors; mod default_theme; +mod players; mod registry; mod scale; mod settings; @@ -14,6 +15,7 @@ use ::settings::Settings; pub use colors::*; pub use default_colors::*; pub use default_theme::*; +pub use players::*; pub use registry::*; pub use scale::*; pub use settings::*; @@ -120,3 +122,8 @@ pub struct DiagnosticStyle { pub hint: Hsla, pub ignored: Hsla, } + +#[cfg(feature = "stories")] +mod story; +#[cfg(feature = "stories")] +pub use story::*; diff --git a/crates/theme_importer/src/theme_printer.rs b/crates/theme_importer/src/theme_printer.rs index aa74692164..0f20a8d60f 100644 --- a/crates/theme_importer/src/theme_printer.rs +++ b/crates/theme_importer/src/theme_printer.rs @@ -140,8 +140,7 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> { ("element_active", self.0.element_active), ("element_selected", self.0.element_selected), ("element_disabled", self.0.element_disabled), - ("element_placeholder", self.0.element_placeholder), - ("element_drop_target", self.0.element_drop_target), + ("drop_target_background", self.0.drop_target_background), ("ghost_element_background", self.0.ghost_element_background), ("ghost_element_hover", self.0.ghost_element_hover), ("ghost_element_active", self.0.ghost_element_active), diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 416db2d172..e936dc924a 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -127,7 +127,7 @@ impl Tab { div() .id(self.id.clone()) .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone())) - .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) + .drag_over::(|d| d.bg(cx.theme().colors().drop_target_background)) .on_drop(|_view, state: View, cx| { eprintln!("{:?}", state.read(cx)); }) diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index e7cee53b2b..01f940273a 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -1,8 +1,8 @@ use std::{any::TypeId, sync::Arc}; use gpui::{ - div, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render, - StatelessInteractive, View, ViewContext, + div, AnyView, AppContext, DispatchPhase, Div, ParentElement, Render, StatelessInteractive, + View, ViewContext, }; use crate::Workspace; @@ -28,10 +28,6 @@ struct ToggleModal { name: String, } -// complete change of plan? -// on_action(ToggleModal{ name}) -// register_modal(name, |workspace, cx| { ... }) - impl ModalRegistry { pub fn register_modal(&mut self, action: A, build_view: B) where @@ -40,12 +36,10 @@ impl ModalRegistry { { let build_view = Arc::new(build_view); - dbg!("yonder"); self.registered_modals.push(( TypeId::of::(), Box::new(move |mut div| { let build_view = build_view.clone(); - dbg!("this point"); div.on_action( move |workspace: &mut Workspace, @@ -75,9 +69,7 @@ impl ModalLayer { Self { open_modal: None } } - pub fn render(&self, cx: &ViewContext) -> impl Component { - dbg!("rendering ModalLayer"); - + pub fn render(&self, workspace: &Workspace, cx: &ViewContext) -> Div { let mut div = div(); // div, c workspace.toggle_modal()div.on_action()) { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 6b8077cd38..b51b0186ef 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) } } @@ -3707,7 +3707,9 @@ impl Render for Workspace { .bg(cx.theme().colors().background) .child(self.render_titlebar(cx)) .child( - div() + self.modal_layer + .read(cx) + .render(self, cx) .flex_1() .w_full() .flex() @@ -3840,8 +3842,6 @@ impl Render for Workspace { // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))), // ), ) - // .child(self.modal_layer.clone()) - .child(self.modal_layer.read(cx).render(cx)) } }