Detailed changes
@@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@v3
with:
clean: false
- submodules: 'recursive'
+ submodules: "recursive"
- name: cargo fmt
run: cargo fmt --all -- --check
@@ -56,13 +56,13 @@ jobs:
- name: Install Node
uses: actions/setup-node@v3
with:
- node-version: '18'
+ node-version: "18"
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
- submodules: 'recursive'
+ submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70
@@ -79,9 +79,6 @@ jobs:
- name: Build other binaries
run: cargo build --workspace --bins --all-features
- - name: Generate license file
- run: script/generate-licenses
-
bundle:
name: Bundle app
runs-on:
@@ -106,13 +103,13 @@ jobs:
- name: Install Node
uses: actions/setup-node@v3
with:
- node-version: '18'
+ node-version: "18"
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
- submodules: 'recursive'
+ submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70
@@ -4988,6 +4988,7 @@ name = "menu2"
version = "0.1.0"
dependencies = [
"gpui2",
+ "serde",
]
[[package]]
@@ -6023,6 +6024,23 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "picker2"
+version = "0.1.0"
+dependencies = [
+ "ctor",
+ "editor2",
+ "env_logger 0.9.3",
+ "gpui2",
+ "menu2",
+ "parking_lot 0.11.2",
+ "serde_json",
+ "settings2",
+ "theme2",
+ "util",
+ "workspace2",
+]
+
[[package]]
name = "pico-args"
version = "0.4.2"
@@ -8539,9 +8557,14 @@ dependencies = [
"backtrace-on-stack-overflow",
"chrono",
"clap 4.4.4",
+ "editor2",
+ "fuzzy2",
"gpui2",
"itertools 0.11.0",
+ "language2",
"log",
+ "menu2",
+ "picker2",
"rust-embed",
"serde",
"settings2",
@@ -9005,6 +9028,7 @@ dependencies = [
"fs2",
"gpui2",
"indexmap 1.9.3",
+ "itertools 0.11.0",
"parking_lot 0.11.2",
"refineable",
"schemars",
@@ -11077,7 +11101,7 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.112.0"
+version = "0.113.0"
dependencies = [
"activity_indicator",
"ai",
@@ -68,6 +68,7 @@ members = [
"crates/notifications",
"crates/outline",
"crates/picker",
+ "crates/picker2",
"crates/plugin",
"crates/plugin_macros",
"crates/plugin_runtime",
@@ -80,7 +80,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
init_settings(cx);
let client = Arc::downgrade(client);
- cx.register_action_type::<SignIn>();
cx.on_action({
let client = client.clone();
move |_: &SignIn, cx| {
@@ -93,7 +92,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
}
});
- cx.register_action_type::<SignOut>();
cx.on_action({
let client = client.clone();
move |_: &SignOut, cx| {
@@ -106,7 +104,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
}
});
- cx.register_action_type::<Reconnect>();
cx.on_action({
let client = client.clone();
move |_: &Reconnect, cx| {
@@ -7,8 +7,8 @@ use async_tar::Archive;
use collections::{HashMap, HashSet};
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
use gpui::{
- AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext,
- Task, WeakModel,
+ actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model,
+ ModelContext, Task, WeakModel,
};
use language::{
language_settings::{all_language_settings, language_settings},
@@ -34,19 +34,11 @@ use util::{
// todo!()
// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
-// actions!(copilot_auth, [SignIn, SignOut]);
+actions!(SignIn, SignOut);
// todo!()
// const COPILOT_NAMESPACE: &'static str = "copilot";
-// actions!(
-// copilot,
-// [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
-// );
-//
-pub struct Suggest;
-pub struct NextSuggestion;
-pub struct PreviousSuggestion;
-pub struct Reinstall;
+actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall);
pub fn init(
new_server_id: LanguageServerId,
@@ -20,12 +20,14 @@ pub mod selections_collection;
mod editor_tests;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
+use ::git::diff::DiffHunk;
use aho_corasick::AhoCorasick;
use anyhow::{Context as _, Result};
use blink_manager::BlinkManager;
use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings};
use clock::ReplicaId;
-use collections::{BTreeMap, HashMap, HashSet, VecDeque};
+use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -35,11 +37,13 @@ pub use element::{
};
use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
+use git::diff_hunk_to_display;
use gpui::{
- actions, div, hsla, px, relative, rems, AnyElement, AppContext, BackgroundExecutor, Context,
- DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontFeatures, FontStyle,
- FontWeight, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View,
- ViewContext, VisualContext, WeakView, WindowContext,
+ action, actions, div, px, relative, rems, AnyElement, AppContext, BackgroundExecutor,
+ ClipboardItem, Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle,
+ FontFeatures, 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};
@@ -50,12 +54,13 @@ pub use language::{char_kind, CharKind};
use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,
- Diagnostic, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
- SelectionGoal, TransactionId,
+ Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
+ OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
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,
@@ -63,26 +68,30 @@ pub use multi_buffer::{
use ordered_float::OrderedFloat;
use parking_lot::RwLock;
use project::{FormatTrigger, Location, Project};
+use rand::prelude::*;
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;
+use snippet::Snippet;
use std::{
any::TypeId,
borrow::Cow,
cmp::{self, Ordering, Reverse},
- ops::{ControlFlow, Deref, DerefMut, Range},
+ mem,
+ num::NonZeroU32,
+ ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path,
sync::Arc,
time::{Duration, Instant},
};
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,
};
@@ -164,87 +173,82 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
// // .with_soft_wrap(true)
// }
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[action]
pub struct SelectNext {
#[serde(default)]
pub replace_newest: bool,
}
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[action]
pub struct SelectPrevious {
#[serde(default)]
pub replace_newest: bool,
}
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[action]
pub struct SelectAllMatches {
#[serde(default)]
pub replace_newest: bool,
}
-#[derive(Clone, Deserialize, PartialEq)]
+#[action]
pub struct SelectToBeginningOfLine {
#[serde(default)]
stop_at_soft_wraps: bool,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct MovePageUp {
#[serde(default)]
center_cursor: bool,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct MovePageDown {
#[serde(default)]
center_cursor: bool,
}
-#[derive(Clone, Deserialize, PartialEq)]
+#[action]
pub struct SelectToEndOfLine {
#[serde(default)]
stop_at_soft_wraps: bool,
}
-#[derive(Clone, Deserialize, PartialEq)]
+#[action]
pub struct ToggleCodeActions {
#[serde(default)]
pub deployed_from_indicator: bool,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct ConfirmCompletion {
#[serde(default)]
pub item_ix: Option<usize>,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct ConfirmCodeAction {
#[serde(default)]
pub item_ix: Option<usize>,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct ToggleComments {
#[serde(default)]
pub advance_downwards: bool,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct FoldAt {
pub buffer_row: u32,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[action]
pub struct UnfoldAt {
pub buffer_row: u32,
}
-#[derive(Clone, Default, Deserialize, PartialEq)]
-pub struct GutterHover {
- pub hovered: bool,
-}
-
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InlayId {
Suggestion(usize),
@@ -261,113 +265,121 @@ impl InlayId {
}
actions!(
- Cancel,
+ AddSelectionAbove,
+ AddSelectionBelow,
Backspace,
+ Cancel,
+ ConfirmRename,
+ ContextMenuFirst,
+ ContextMenuLast,
+ ContextMenuNext,
+ ContextMenuPrev,
+ ConvertToKebabCase,
+ ConvertToLowerCamelCase,
+ ConvertToLowerCase,
+ ConvertToSnakeCase,
+ ConvertToTitleCase,
+ ConvertToUpperCamelCase,
+ ConvertToUpperCase,
+ Copy,
+ CopyHighlightJson,
+ CopyPath,
+ CopyRelativePath,
+ Cut,
+ CutToEndOfLine,
Delete,
- Newline,
- NewlineAbove,
- NewlineBelow,
- GoToDiagnostic,
- GoToPrevDiagnostic,
- GoToHunk,
- GoToPrevHunk,
- Indent,
- Outdent,
DeleteLine,
- DeleteToPreviousWordStart,
- DeleteToPreviousSubwordStart,
- DeleteToNextWordEnd,
- DeleteToNextSubwordEnd,
DeleteToBeginningOfLine,
DeleteToEndOfLine,
- CutToEndOfLine,
+ DeleteToNextSubwordEnd,
+ DeleteToNextWordEnd,
+ DeleteToPreviousSubwordStart,
+ DeleteToPreviousWordStart,
DuplicateLine,
- MoveLineUp,
- MoveLineDown,
+ FindAllReferences,
+ Fold,
+ FoldSelectedRanges,
+ Format,
+ GoToDefinition,
+ GoToDefinitionSplit,
+ GoToDiagnostic,
+ GoToHunk,
+ GoToPrevDiagnostic,
+ GoToPrevHunk,
+ GoToTypeDefinition,
+ GoToTypeDefinitionSplit,
+ HalfPageDown,
+ HalfPageUp,
+ Hover,
+ Indent,
JoinLines,
- SortLinesCaseSensitive,
- SortLinesCaseInsensitive,
- ReverseLines,
- ShuffleLines,
- ConvertToUpperCase,
- ConvertToLowerCase,
- ConvertToTitleCase,
- ConvertToSnakeCase,
- ConvertToKebabCase,
- ConvertToUpperCamelCase,
- ConvertToLowerCamelCase,
- Transpose,
- Cut,
- Copy,
- Paste,
- Undo,
- Redo,
- MoveUp,
- PageUp,
+ LineDown,
+ LineUp,
MoveDown,
- PageDown,
MoveLeft,
+ MoveLineDown,
+ MoveLineUp,
MoveRight,
- MoveToPreviousWordStart,
- MoveToPreviousSubwordStart,
- MoveToNextWordEnd,
- MoveToNextSubwordEnd,
+ MoveToBeginning,
MoveToBeginningOfLine,
+ MoveToEnclosingBracket,
+ MoveToEnd,
MoveToEndOfLine,
- MoveToStartOfParagraph,
MoveToEndOfParagraph,
- MoveToBeginning,
- MoveToEnd,
- SelectUp,
+ MoveToNextSubwordEnd,
+ MoveToNextWordEnd,
+ MoveToPreviousSubwordStart,
+ MoveToPreviousWordStart,
+ MoveToStartOfParagraph,
+ MoveUp,
+ Newline,
+ NewlineAbove,
+ NewlineBelow,
+ NextScreen,
+ OpenExcerpts,
+ Outdent,
+ PageDown,
+ PageUp,
+ Paste,
+ Redo,
+ RedoSelection,
+ Rename,
+ RestartLanguageServer,
+ RevealInFinder,
+ ReverseLines,
+ ScrollCursorBottom,
+ ScrollCursorCenter,
+ ScrollCursorTop,
+ SelectAll,
SelectDown,
+ SelectLargerSyntaxNode,
SelectLeft,
+ SelectLine,
SelectRight,
- SelectToPreviousWordStart,
- SelectToPreviousSubwordStart,
- SelectToNextWordEnd,
- SelectToNextSubwordEnd,
- SelectToStartOfParagraph,
- SelectToEndOfParagraph,
+ SelectSmallerSyntaxNode,
SelectToBeginning,
SelectToEnd,
- SelectAll,
- SelectLine,
+ SelectToEndOfParagraph,
+ SelectToNextSubwordEnd,
+ SelectToNextWordEnd,
+ SelectToPreviousSubwordStart,
+ SelectToPreviousWordStart,
+ SelectToStartOfParagraph,
+ SelectUp,
+ ShowCharacterPalette,
+ ShowCompletions,
+ ShuffleLines,
+ SortLinesCaseInsensitive,
+ SortLinesCaseSensitive,
SplitSelectionIntoLines,
- AddSelectionAbove,
- AddSelectionBelow,
Tab,
TabPrev,
- ShowCharacterPalette,
- SelectLargerSyntaxNode,
- SelectSmallerSyntaxNode,
- GoToDefinition,
- GoToDefinitionSplit,
- GoToTypeDefinition,
- GoToTypeDefinitionSplit,
- MoveToEnclosingBracket,
+ ToggleInlayHints,
+ ToggleSoftWrap,
+ Transpose,
+ Undo,
UndoSelection,
- RedoSelection,
- FindAllReferences,
- Rename,
- ConfirmRename,
- Fold,
UnfoldLines,
- FoldSelectedRanges,
- ShowCompletions,
- OpenExcerpts,
- RestartLanguageServer,
- Hover,
- Format,
- ToggleSoftWrap,
- ToggleInlayHints,
- RevealInFinder,
- CopyPath,
- CopyRelativePath,
- CopyHighlightJson,
- ContextMenuFirst,
- ContextMenuPrev,
- ContextMenuNext,
- ContextMenuLast,
);
// impl_actions!(
@@ -447,13 +459,12 @@ pub fn init(cx: &mut AppContext) {
// cx.register_action_type(Editor::paste);
// cx.register_action_type(Editor::undo);
// cx.register_action_type(Editor::redo);
- cx.register_action_type::<MoveUp>();
// cx.register_action_type(Editor::move_page_up);
- cx.register_action_type::<MoveDown>();
+ // cx.register_action_type::<MoveDown>();
// cx.register_action_type(Editor::move_page_down);
// cx.register_action_type(Editor::next_screen);
- cx.register_action_type::<MoveLeft>();
- cx.register_action_type::<MoveRight>();
+ // cx.register_action_type::<MoveLeft>();
+ // cx.register_action_type::<MoveRight>();
// cx.register_action_type(Editor::move_to_previous_word_start);
// cx.register_action_type(Editor::move_to_previous_subword_start);
// cx.register_action_type(Editor::move_to_next_word_end);
@@ -532,7 +543,6 @@ pub fn init(cx: &mut AppContext) {
// cx.register_action_type(Editor::context_menu_last);
hover_popover::init(cx);
- scroll::actions::init(cx);
workspace::register_project_item::<Editor>(cx);
workspace::register_followable_item::<Editor>(cx);
@@ -988,7 +998,7 @@ impl CompletionsMenu {
project: Option<Model<Project>>,
cx: &mut ViewContext<Editor>,
) {
- todo!("implementation below ");
+ // todo!("implementation below ");
}
// ) {
// let settings = EditorSettings::get_global(cx);
@@ -1155,7 +1165,7 @@ impl CompletionsMenu {
client: Arc<Client>,
language_registry: Arc<LanguageRegistry>,
) {
- todo!()
+ // todo!()
// let request = proto::ResolveCompletionDocumentation {
// project_id,
// language_server_id: server_id.0 as u64,
@@ -1199,7 +1209,7 @@ impl CompletionsMenu {
completion: lsp::CompletionItem,
language_registry: Arc<LanguageRegistry>,
) {
- todo!()
+ // todo!()
// let can_resolve = server
// .capabilities()
// .completion_provider
@@ -2383,45 +2393,45 @@ impl Editor {
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
- // pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
- // where
- // I: IntoIterator<Item = (Range<S>, T)>,
- // S: ToOffset,
- // T: Into<Arc<str>>,
- // {
- // if self.read_only {
- // return;
- // }
+ pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
+ where
+ I: IntoIterator<Item = (Range<S>, T)>,
+ S: ToOffset,
+ T: Into<Arc<str>>,
+ {
+ if self.read_only {
+ return;
+ }
- // self.buffer.update(cx, |buffer, cx| {
- // buffer.edit(edits, self.autoindent_mode.clone(), cx)
- // });
- // }
+ self.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, self.autoindent_mode.clone(), cx)
+ });
+ }
- // pub fn edit_with_block_indent<I, S, T>(
- // &mut self,
- // edits: I,
- // original_indent_columns: Vec<u32>,
- // cx: &mut ViewContext<Self>,
- // ) where
- // I: IntoIterator<Item = (Range<S>, T)>,
- // S: ToOffset,
- // T: Into<Arc<str>>,
- // {
- // if self.read_only {
- // return;
- // }
+ pub fn edit_with_block_indent<I, S, T>(
+ &mut self,
+ edits: I,
+ original_indent_columns: Vec<u32>,
+ cx: &mut ViewContext<Self>,
+ ) where
+ I: IntoIterator<Item = (Range<S>, T)>,
+ S: ToOffset,
+ T: Into<Arc<str>>,
+ {
+ if self.read_only {
+ return;
+ }
- // self.buffer.update(cx, |buffer, cx| {
- // buffer.edit(
- // edits,
- // Some(AutoindentMode::Block {
- // original_indent_columns,
- // }),
- // cx,
- // )
- // });
- // }
+ self.buffer.update(cx, |buffer, cx| {
+ buffer.edit(
+ edits,
+ Some(AutoindentMode::Block {
+ original_indent_columns,
+ }),
+ cx,
+ )
+ });
+ }
fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
self.hide_context_menu(cx);
@@ -2734,490 +2744,490 @@ impl Editor {
self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
}
- // pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- // if self.take_rename(false, cx).is_some() {
- // return;
- // }
+ pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+ if self.take_rename(false, cx).is_some() {
+ return;
+ }
- // if hide_hover(self, cx) {
- // return;
- // }
+ if hide_hover(self, cx) {
+ return;
+ }
- // if self.hide_context_menu(cx).is_some() {
- // return;
- // }
+ if self.hide_context_menu(cx).is_some() {
+ return;
+ }
- // if self.discard_copilot_suggestion(cx) {
- // return;
- // }
+ if self.discard_copilot_suggestion(cx) {
+ return;
+ }
- // if self.snippet_stack.pop().is_some() {
- // return;
- // }
+ if self.snippet_stack.pop().is_some() {
+ return;
+ }
- // if self.mode == EditorMode::Full {
- // if self.active_diagnostics.is_some() {
- // self.dismiss_diagnostics(cx);
- // return;
- // }
+ if self.mode == EditorMode::Full {
+ if self.active_diagnostics.is_some() {
+ self.dismiss_diagnostics(cx);
+ return;
+ }
- // if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
- // return;
- // }
- // }
+ if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
+ return;
+ }
+ }
- // cx.propagate();
- // }
+ cx.propagate();
+ }
- // pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
- // let text: Arc<str> = text.into();
+ pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
+ let text: Arc<str> = 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::<usize, _>(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::<Vec<_>>();
-
- // 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 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::<usize, _>(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::<Vec<_>>();
- // 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));
+ 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,
+ },
+ );
+ }
- // 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);
- // }
- // }
+ 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 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 !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);
+ }
+ }
- // pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
- // self.transact(cx, |this, cx| {
- // let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
- // let selections = this.selections.all::<usize>(cx);
- // let multi_buffer = this.buffer.read(cx);
- // let buffer = multi_buffer.snapshot(cx);
- // selections
- // .iter()
- // .map(|selection| {
- // let start_point = selection.start.to_point(&buffer);
- // let mut indent = buffer.indent_size_for_line(start_point.row);
- // indent.len = cmp::min(indent.len, start_point.column);
- // let start = selection.start;
- // let end = selection.end;
- // let is_cursor = start == end;
- // let language_scope = buffer.language_scope_at(start);
- // let (comment_delimiter, insert_extra_newline) = if let Some(language) =
- // &language_scope
- // {
- // let leading_whitespace_len = buffer
- // .reversed_chars_at(start)
- // .take_while(|c| c.is_whitespace() && *c != '\n')
- // .map(|c| c.len_utf8())
- // .sum::<usize>();
-
- // let trailing_whitespace_len = buffer
- // .chars_at(end)
- // .take_while(|c| c.is_whitespace() && *c != '\n')
- // .map(|c| c.len_utf8())
- // .sum::<usize>();
-
- // let insert_extra_newline =
- // language.brackets().any(|(pair, enabled)| {
- // let pair_start = pair.start.trim_end();
- // let pair_end = pair.end.trim_start();
-
- // enabled
- // && pair.newline
- // && buffer.contains_str_at(
- // end + trailing_whitespace_len,
- // pair_end,
- // )
- // && buffer.contains_str_at(
- // (start - leading_whitespace_len)
- // .saturating_sub(pair_start.len()),
- // pair_start,
- // )
- // });
- // // Comment extension on newline is allowed only for cursor selections
- // let comment_delimiter = language.line_comment_prefix().filter(|_| {
- // let is_comment_extension_enabled =
- // multi_buffer.settings_at(0, cx).extend_comment_on_newline;
- // is_cursor && is_comment_extension_enabled
- // });
- // let comment_delimiter = if let Some(delimiter) = comment_delimiter {
- // buffer
- // .buffer_line_for_row(start_point.row)
- // .is_some_and(|(snapshot, range)| {
- // let mut index_of_first_non_whitespace = 0;
- // let line_starts_with_comment = snapshot
- // .chars_for_range(range)
- // .skip_while(|c| {
- // let should_skip = c.is_whitespace();
- // if should_skip {
- // index_of_first_non_whitespace += 1;
- // }
- // should_skip
- // })
- // .take(delimiter.len())
- // .eq(delimiter.chars());
- // let cursor_is_placed_after_comment_marker =
- // index_of_first_non_whitespace + delimiter.len()
- // <= start_point.column as usize;
- // line_starts_with_comment
- // && cursor_is_placed_after_comment_marker
- // })
- // .then(|| delimiter.clone())
- // } else {
- // None
- // };
- // (comment_delimiter, insert_extra_newline)
- // } else {
- // (None, false)
- // };
+ 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);
+ }
+ });
+ }
- // let capacity_for_delimiter = comment_delimiter
- // .as_deref()
- // .map(str::len)
- // .unwrap_or_default();
- // let mut new_text =
- // String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
- // new_text.push_str("\n");
- // new_text.extend(indent.chars());
- // if let Some(delimiter) = &comment_delimiter {
- // new_text.push_str(&delimiter);
- // }
- // if insert_extra_newline {
- // new_text = new_text.repeat(2);
- // }
+ pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
+ self.transact(cx, |this, cx| {
+ let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
+ let selections = this.selections.all::<usize>(cx);
+ let multi_buffer = this.buffer.read(cx);
+ let buffer = multi_buffer.snapshot(cx);
+ selections
+ .iter()
+ .map(|selection| {
+ let start_point = selection.start.to_point(&buffer);
+ let mut indent = buffer.indent_size_for_line(start_point.row);
+ indent.len = cmp::min(indent.len, start_point.column);
+ let start = selection.start;
+ let end = selection.end;
+ let is_cursor = start == end;
+ let language_scope = buffer.language_scope_at(start);
+ let (comment_delimiter, insert_extra_newline) = if let Some(language) =
+ &language_scope
+ {
+ let leading_whitespace_len = buffer
+ .reversed_chars_at(start)
+ .take_while(|c| c.is_whitespace() && *c != '\n')
+ .map(|c| c.len_utf8())
+ .sum::<usize>();
+
+ let trailing_whitespace_len = buffer
+ .chars_at(end)
+ .take_while(|c| c.is_whitespace() && *c != '\n')
+ .map(|c| c.len_utf8())
+ .sum::<usize>();
+
+ let insert_extra_newline =
+ language.brackets().any(|(pair, enabled)| {
+ let pair_start = pair.start.trim_end();
+ let pair_end = pair.end.trim_start();
+
+ enabled
+ && pair.newline
+ && buffer.contains_str_at(
+ end + trailing_whitespace_len,
+ pair_end,
+ )
+ && buffer.contains_str_at(
+ (start - leading_whitespace_len)
+ .saturating_sub(pair_start.len()),
+ pair_start,
+ )
+ });
+ // Comment extension on newline is allowed only for cursor selections
+ let comment_delimiter = language.line_comment_prefix().filter(|_| {
+ let is_comment_extension_enabled =
+ multi_buffer.settings_at(0, cx).extend_comment_on_newline;
+ is_cursor && is_comment_extension_enabled
+ });
+ let comment_delimiter = if let Some(delimiter) = comment_delimiter {
+ buffer
+ .buffer_line_for_row(start_point.row)
+ .is_some_and(|(snapshot, range)| {
+ let mut index_of_first_non_whitespace = 0;
+ let line_starts_with_comment = snapshot
+ .chars_for_range(range)
+ .skip_while(|c| {
+ let should_skip = c.is_whitespace();
+ if should_skip {
+ index_of_first_non_whitespace += 1;
+ }
+ should_skip
+ })
+ .take(delimiter.len())
+ .eq(delimiter.chars());
+ let cursor_is_placed_after_comment_marker =
+ index_of_first_non_whitespace + delimiter.len()
+ <= start_point.column as usize;
+ line_starts_with_comment
+ && cursor_is_placed_after_comment_marker
+ })
+ .then(|| delimiter.clone())
+ } else {
+ None
+ };
+ (comment_delimiter, insert_extra_newline)
+ } else {
+ (None, false)
+ };
+
+ let capacity_for_delimiter = comment_delimiter
+ .as_deref()
+ .map(str::len)
+ .unwrap_or_default();
+ let mut new_text =
+ String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
+ new_text.push_str("\n");
+ new_text.extend(indent.chars());
+ if let Some(delimiter) = &comment_delimiter {
+ new_text.push_str(&delimiter);
+ }
+ if insert_extra_newline {
+ new_text = new_text.repeat(2);
+ }
- // let anchor = buffer.anchor_after(end);
- // let new_selection = selection.map(|_| anchor);
- // (
- // (start..end, new_text),
- // (insert_extra_newline, new_selection),
- // )
- // })
- // .unzip()
- // };
+ let anchor = buffer.anchor_after(end);
+ let new_selection = selection.map(|_| anchor);
+ (
+ (start..end, new_text),
+ (insert_extra_newline, new_selection),
+ )
+ })
+ .unzip()
+ };
- // this.edit_with_autoindent(edits, cx);
- // let buffer = this.buffer.read(cx).snapshot(cx);
- // let new_selections = selection_fixup_info
- // .into_iter()
- // .map(|(extra_newline_inserted, new_selection)| {
- // let mut cursor = new_selection.end.to_point(&buffer);
- // if extra_newline_inserted {
- // cursor.row -= 1;
- // cursor.column = buffer.line_len(cursor.row);
- // }
- // new_selection.map(|_| cursor)
- // })
- // .collect();
+ this.edit_with_autoindent(edits, cx);
+ let buffer = this.buffer.read(cx).snapshot(cx);
+ let new_selections = selection_fixup_info
+ .into_iter()
+ .map(|(extra_newline_inserted, new_selection)| {
+ let mut cursor = new_selection.end.to_point(&buffer);
+ if extra_newline_inserted {
+ cursor.row -= 1;
+ cursor.column = buffer.line_len(cursor.row);
+ }
+ new_selection.map(|_| cursor)
+ })
+ .collect();
- // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
- // this.refresh_copilot_suggestions(true, cx);
- // });
- // }
+ this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
+ this.refresh_copilot_suggestions(true, cx);
+ });
+ }
- // pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
- // let buffer = self.buffer.read(cx);
- // let snapshot = buffer.snapshot(cx);
+ pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
+ let buffer = self.buffer.read(cx);
+ let snapshot = buffer.snapshot(cx);
- // let mut edits = Vec::new();
- // let mut rows = Vec::new();
- // let mut rows_inserted = 0;
+ let mut edits = Vec::new();
+ let mut rows = Vec::new();
+ let mut rows_inserted = 0;
- // for selection in self.selections.all_adjusted(cx) {
- // let cursor = selection.head();
- // let row = cursor.row;
+ for selection in self.selections.all_adjusted(cx) {
+ let cursor = selection.head();
+ let row = cursor.row;
- // let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
+ let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
- // let newline = "\n".to_string();
- // edits.push((start_of_line..start_of_line, newline));
+ let newline = "\n".to_string();
+ edits.push((start_of_line..start_of_line, newline));
- // rows.push(row + rows_inserted);
- // rows_inserted += 1;
- // }
+ rows.push(row + rows_inserted);
+ rows_inserted += 1;
+ }
- // self.transact(cx, |editor, cx| {
- // editor.edit(edits, cx);
+ self.transact(cx, |editor, cx| {
+ editor.edit(edits, cx);
- // editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- // let mut index = 0;
- // s.move_cursors_with(|map, _, _| {
- // let row = rows[index];
- // index += 1;
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ let mut index = 0;
+ s.move_cursors_with(|map, _, _| {
+ let row = rows[index];
+ index += 1;
- // let point = Point::new(row, 0);
- // let boundary = map.next_line_boundary(point).1;
- // let clipped = map.clip_point(boundary, Bias::Left);
+ let point = Point::new(row, 0);
+ let boundary = map.next_line_boundary(point).1;
+ let clipped = map.clip_point(boundary, Bias::Left);
- // (clipped, SelectionGoal::None)
- // });
- // });
+ (clipped, SelectionGoal::None)
+ });
+ });
- // let mut indent_edits = Vec::new();
- // let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
- // for row in rows {
- // let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
- // for (row, indent) in indents {
- // if indent.len == 0 {
- // continue;
- // }
+ let mut indent_edits = Vec::new();
+ let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
+ for row in rows {
+ let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
+ for (row, indent) in indents {
+ if indent.len == 0 {
+ continue;
+ }
- // let text = match indent.kind {
- // IndentKind::Space => " ".repeat(indent.len as usize),
- // IndentKind::Tab => "\t".repeat(indent.len as usize),
- // };
- // let point = Point::new(row, 0);
- // indent_edits.push((point..point, text));
- // }
- // }
- // editor.edit(indent_edits, cx);
- // });
- // }
+ let text = match indent.kind {
+ IndentKind::Space => " ".repeat(indent.len as usize),
+ IndentKind::Tab => "\t".repeat(indent.len as usize),
+ };
+ let point = Point::new(row, 0);
+ indent_edits.push((point..point, text));
+ }
+ }
+ editor.edit(indent_edits, cx);
+ });
+ }
- // pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
- // let buffer = self.buffer.read(cx);
- // let snapshot = buffer.snapshot(cx);
+ pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
+ let buffer = self.buffer.read(cx);
+ let snapshot = buffer.snapshot(cx);
- // let mut edits = Vec::new();
- // let mut rows = Vec::new();
- // let mut rows_inserted = 0;
+ let mut edits = Vec::new();
+ let mut rows = Vec::new();
+ let mut rows_inserted = 0;
- // for selection in self.selections.all_adjusted(cx) {
- // let cursor = selection.head();
- // let row = cursor.row;
+ for selection in self.selections.all_adjusted(cx) {
+ let cursor = selection.head();
+ let row = cursor.row;
- // let point = Point::new(row + 1, 0);
- // let start_of_line = snapshot.clip_point(point, Bias::Left);
+ let point = Point::new(row + 1, 0);
+ let start_of_line = snapshot.clip_point(point, Bias::Left);
- // let newline = "\n".to_string();
- // edits.push((start_of_line..start_of_line, newline));
+ let newline = "\n".to_string();
+ edits.push((start_of_line..start_of_line, newline));
- // rows_inserted += 1;
- // rows.push(row + rows_inserted);
- // }
+ rows_inserted += 1;
+ rows.push(row + rows_inserted);
+ }
- // self.transact(cx, |editor, cx| {
- // editor.edit(edits, cx);
+ self.transact(cx, |editor, cx| {
+ editor.edit(edits, cx);
- // editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- // let mut index = 0;
- // s.move_cursors_with(|map, _, _| {
- // let row = rows[index];
- // index += 1;
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ let mut index = 0;
+ s.move_cursors_with(|map, _, _| {
+ let row = rows[index];
+ index += 1;
- // let point = Point::new(row, 0);
- // let boundary = map.next_line_boundary(point).1;
- // let clipped = map.clip_point(boundary, Bias::Left);
+ let point = Point::new(row, 0);
+ let boundary = map.next_line_boundary(point).1;
+ let clipped = map.clip_point(boundary, Bias::Left);
- // (clipped, SelectionGoal::None)
- // });
- // });
+ (clipped, SelectionGoal::None)
+ });
+ });
- // let mut indent_edits = Vec::new();
- // let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
- // for row in rows {
- // let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
- // for (row, indent) in indents {
- // if indent.len == 0 {
- // continue;
- // }
+ let mut indent_edits = Vec::new();
+ let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
+ for row in rows {
+ let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
+ for (row, indent) in indents {
+ if indent.len == 0 {
+ continue;
+ }
- // let text = match indent.kind {
- // IndentKind::Space => " ".repeat(indent.len as usize),
- // IndentKind::Tab => "\t".repeat(indent.len as usize),
- // };
- // let point = Point::new(row, 0);
- // indent_edits.push((point..point, text));
- // }
- // }
- // editor.edit(indent_edits, cx);
- // });
- // }
+ let text = match indent.kind {
+ IndentKind::Space => " ".repeat(indent.len as usize),
+ IndentKind::Tab => "\t".repeat(indent.len as usize),
+ };
+ let point = Point::new(row, 0);
+ indent_edits.push((point..point, text));
+ }
+ }
+ editor.edit(indent_edits, cx);
+ });
+ }
- // pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
- // self.insert_with_autoindent_mode(
- // text,
- // Some(AutoindentMode::Block {
- // original_indent_columns: Vec::new(),
- // }),
- // cx,
- // );
- // }
+ pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
+ self.insert_with_autoindent_mode(
+ text,
+ Some(AutoindentMode::Block {
+ original_indent_columns: Vec::new(),
+ }),
+ cx,
+ );
+ }
fn insert_with_autoindent_mode(
&mut self,
@@ -2,17 +2,25 @@ use crate::{
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint},
editor_settings::ShowScrollbar,
git::{diff_hunk_to_display, DisplayDiffHunk},
+ hover_popover::hover_at,
+ link_go_to_definition::{
+ go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
+ update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+ },
+ scroll::scroll_amount::ScrollAmount,
CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
- MoveDown, Point, Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
+ HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, PageDown, PageUp, Point, SelectPhase,
+ Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
};
use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
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,
+ Edges, Element, ElementId, Entity, GlobalElementId, Hsla, KeyDownEvent, KeyListener, KeyMatch,
+ Line, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+ ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext,
+ WindowContext,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -31,6 +39,7 @@ use std::{
};
use sum_tree::Bias;
use theme::{ActiveTheme, PlayerColor};
+use util::ResultExt;
use workspace::item::Item;
enum FoldMarkers {}
@@ -104,194 +113,54 @@ impl EditorElement {
Self { style }
}
- // fn attach_mouse_handlers(
- // position_map: &Arc<PositionMap>,
- // has_popovers: bool,
- // visible_bounds: Bounds<Pixels>,
- // text_bounds: Bounds<Pixels>,
- // gutter_bounds: Bounds<Pixels>,
- // bounds: Bounds<Pixels>,
- // cx: &mut ViewContext<Editor>,
- // ) {
- // enum EditorElementMouseHandlers {}
- // let view_id = cx.view_id();
- // cx.scene().push_mouse_region(
- // MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
- // .on_down(MouseButton::Left, {
- // let position_map = position_map.clone();
- // move |event, editor, cx| {
- // if !Self::mouse_down(
- // editor,
- // event.platform_event,
- // position_map.as_ref(),
- // text_bounds,
- // gutter_bounds,
- // cx,
- // ) {
- // cx.propagate_event();
- // }
- // }
- // })
- // .on_down(MouseButton::Right, {
- // let position_map = position_map.clone();
- // move |event, editor, cx| {
- // if !Self::mouse_right_down(
- // editor,
- // event.position,
- // position_map.as_ref(),
- // text_bounds,
- // cx,
- // ) {
- // cx.propagate_event();
- // }
- // }
- // })
- // .on_up(MouseButton::Left, {
- // let position_map = position_map.clone();
- // move |event, editor, cx| {
- // if !Self::mouse_up(
- // editor,
- // event.position,
- // event.cmd,
- // event.shift,
- // event.alt,
- // position_map.as_ref(),
- // text_bounds,
- // cx,
- // ) {
- // cx.propagate_event()
- // }
- // }
- // })
- // .on_drag(MouseButton::Left, {
- // let position_map = position_map.clone();
- // move |event, editor, cx| {
- // if event.end {
- // return;
- // }
-
- // if !Self::mouse_dragged(
- // editor,
- // event.platform_event,
- // position_map.as_ref(),
- // text_bounds,
- // cx,
- // ) {
- // cx.propagate_event()
- // }
- // }
- // })
- // .on_move({
- // let position_map = position_map.clone();
- // move |event, editor, cx| {
- // if !Self::mouse_moved(
- // editor,
- // event.platform_event,
- // &position_map,
- // text_bounds,
- // cx,
- // ) {
- // cx.propagate_event()
- // }
- // }
- // })
- // .on_move_out(move |_, editor: &mut Editor, cx| {
- // if has_popovers {
- // hide_hover(editor, cx);
- // }
- // })
- // .on_scroll({
- // let position_map = position_map.clone();
- // move |event, editor, cx| {
- // if !Self::scroll(
- // editor,
- // event.position,
- // *event.delta.raw(),
- // event.delta.precise(),
- // &position_map,
- // bounds,
- // cx,
- // ) {
- // cx.propagate_event()
- // }
- // }
- // }),
- // );
-
- // enum GutterHandlers {}
- // let view_id = cx.view_id();
- // let region_id = cx.view_id() + 1;
- // cx.scene().push_mouse_region(
- // MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
- // |hover, editor: &mut Editor, cx| {
- // editor.gutter_hover(
- // &GutterHover {
- // hovered: hover.started,
- // },
- // cx,
- // );
- // },
- // ),
- // )
- // }
+ fn mouse_down(
+ editor: &mut Editor,
+ event: &MouseDownEvent,
+ position_map: &PositionMap,
+ text_bounds: Bounds<Pixels>,
+ gutter_bounds: Bounds<Pixels>,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ let mut click_count = event.click_count;
+ let modifiers = event.modifiers;
- // fn mouse_down(
- // editor: &mut Editor,
- // MouseButtonEvent {
- // position,
- // modifiers:
- // Modifiers {
- // shift,
- // ctrl,
- // alt,
- // cmd,
- // ..
- // },
- // mut click_count,
- // ..
- // }: MouseButtonEvent,
- // position_map: &PositionMap,
- // text_bounds: Bounds<Pixels>,
- // gutter_bounds: Bounds<Pixels>,
- // cx: &mut EventContext<Editor>,
- // ) -> bool {
- // if gutter_bounds.contains_point(position) {
- // click_count = 3; // Simulate triple-click when clicking the gutter to select lines
- // } else if !text_bounds.contains_point(position) {
- // return false;
- // }
+ if gutter_bounds.contains_point(&event.position) {
+ click_count = 3; // Simulate triple-click when clicking the gutter to select lines
+ } else if !text_bounds.contains_point(&event.position) {
+ return false;
+ }
- // let point_for_position = position_map.point_for_position(text_bounds, position);
- // let position = point_for_position.previous_valid;
- // if shift && alt {
- // editor.select(
- // SelectPhase::BeginColumnar {
- // position,
- // goal_column: point_for_position.exact_unclipped.column(),
- // },
- // cx,
- // );
- // } else if shift && !ctrl && !alt && !cmd {
- // editor.select(
- // SelectPhase::Extend {
- // position,
- // click_count,
- // },
- // cx,
- // );
- // } else {
- // editor.select(
- // SelectPhase::Begin {
- // position,
- // add: alt,
- // click_count,
- // },
- // cx,
- // );
- // }
+ let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ let position = point_for_position.previous_valid;
+ if modifiers.shift && modifiers.alt {
+ editor.select(
+ SelectPhase::BeginColumnar {
+ position,
+ goal_column: point_for_position.exact_unclipped.column(),
+ },
+ cx,
+ );
+ } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
+ editor.select(
+ SelectPhase::Extend {
+ position,
+ click_count,
+ },
+ cx,
+ );
+ } else {
+ editor.select(
+ SelectPhase::Begin {
+ position,
+ add: modifiers.alt,
+ click_count,
+ },
+ cx,
+ );
+ }
- // true
- // }
+ true
+ }
// fn mouse_right_down(
// editor: &mut Editor,
@@ -313,157 +182,120 @@ impl EditorElement {
// true
// }
- // fn mouse_up(
- // editor: &mut Editor,
- // position: gpui::Point<Pixels>,
- // cmd: bool,
- // shift: bool,
- // alt: bool,
- // position_map: &PositionMap,
- // text_bounds: Bounds<Pixels>,
- // cx: &mut EventContext<Editor>,
- // ) -> bool {
- // let end_selection = editor.has_pending_selection();
- // let pending_nonempty_selections = editor.has_pending_nonempty_selection();
-
- // if end_selection {
- // editor.select(SelectPhase::End, cx);
- // }
-
- // if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
- // let point = position_map.point_for_position(text_bounds, position);
- // let could_be_inlay = point.as_valid().is_none();
- // if shift || could_be_inlay {
- // go_to_fetched_type_definition(editor, point, alt, cx);
- // } else {
- // go_to_fetched_definition(editor, point, alt, cx);
- // }
-
- // return true;
- // }
-
- // end_selection
- // }
+ fn mouse_up(
+ editor: &mut Editor,
+ event: &MouseUpEvent,
+ position_map: &PositionMap,
+ text_bounds: Bounds<Pixels>,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ let end_selection = editor.has_pending_selection();
+ let pending_nonempty_selections = editor.has_pending_nonempty_selection();
- // fn mouse_dragged(
- // editor: &mut Editor,
- // MouseMovedEvent {
- // modifiers: Modifiers { cmd, shift, .. },
- // position,
- // ..
- // }: MouseMovedEvent,
- // position_map: &PositionMap,
- // text_bounds: Bounds<Pixels>,
- // cx: &mut EventContext<Editor>,
- // ) -> bool {
- // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
- // // Don't trigger hover popover if mouse is hovering over context menu
- // let point = if text_bounds.contains_point(position) {
- // position_map
- // .point_for_position(text_bounds, position)
- // .as_valid()
- // } else {
- // None
- // };
+ if end_selection {
+ editor.select(SelectPhase::End, cx);
+ }
- // update_go_to_definition_link(
- // editor,
- // point.map(GoToDefinitionTrigger::Text),
- // cmd,
- // shift,
- // cx,
- // );
+ if !pending_nonempty_selections
+ && event.modifiers.command
+ && text_bounds.contains_point(&event.position)
+ {
+ let point = position_map.point_for_position(text_bounds, event.position);
+ let could_be_inlay = point.as_valid().is_none();
+ let split = event.modifiers.alt;
+ if event.modifiers.shift || could_be_inlay {
+ go_to_fetched_type_definition(editor, point, split, cx);
+ } else {
+ go_to_fetched_definition(editor, point, split, cx);
+ }
- // if editor.has_pending_selection() {
- // let mut scroll_delta = gpui::Point::<Pixels>::zero();
+ return true;
+ }
- // let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
- // let top = text_bounds.origin.y + vertical_margin;
- // let bottom = text_bounds.lower_left().y - vertical_margin;
- // if position.y < top {
- // scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y))
- // }
- // if position.y > bottom {
- // scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y - bottom))
- // }
+ end_selection
+ }
- // let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
- // let left = text_bounds.origin.x + horizontal_margin;
- // let right = text_bounds.upper_right().x - horizontal_margin;
- // if position.x < left {
- // scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
- // left - position.x,
- // ))
- // }
- // if position.x > right {
- // scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
- // position.x - right,
- // ))
- // }
+ fn mouse_moved(
+ editor: &mut Editor,
+ event: &MouseMoveEvent,
+ position_map: &PositionMap,
+ text_bounds: Bounds<Pixels>,
+ gutter_bounds: Bounds<Pixels>,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ let modifiers = event.modifiers;
+ if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
+ let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ let mut scroll_delta = gpui::Point::<f32>::zero();
+ let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
+ let top = text_bounds.origin.y + vertical_margin;
+ let bottom = text_bounds.lower_left().y - vertical_margin;
+ if event.position.y < top {
+ scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
+ }
+ if event.position.y > bottom {
+ scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
+ }
- // let point_for_position = position_map.point_for_position(text_bounds, position);
+ let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
+ let left = text_bounds.origin.x + horizontal_margin;
+ let right = text_bounds.upper_right().x - horizontal_margin;
+ if event.position.x < left {
+ scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
+ }
+ if event.position.x > right {
+ scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
+ }
- // editor.select(
- // SelectPhase::Update {
- // position: point_for_position.previous_valid,
- // goal_column: point_for_position.exact_unclipped.column(),
- // scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
- // .clamp(gpui::Point::<Pixels>::zero(), position_map.scroll_max),
- // },
- // cx,
- // );
- // hover_at(editor, point, cx);
- // true
- // } else {
- // hover_at(editor, point, cx);
- // false
- // }
- // }
+ editor.select(
+ SelectPhase::Update {
+ position: point_for_position.previous_valid,
+ goal_column: point_for_position.exact_unclipped.column(),
+ scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
+ .clamp(&gpui::Point::zero(), &position_map.scroll_max),
+ },
+ cx,
+ );
+ }
- // fn mouse_moved(
- // editor: &mut Editor,
- // MouseMovedEvent {
- // modifiers: Modifiers { shift, cmd, .. },
- // position,
- // ..
- // }: MouseMovedEvent,
- // position_map: &PositionMap,
- // text_bounds: Bounds<Pixels>,
- // cx: &mut ViewContext<Editor>,
- // ) -> bool {
- // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
- // // Don't trigger hover popover if mouse is hovering over context menu
- // if text_bounds.contains_point(position) {
- // let point_for_position = position_map.point_for_position(text_bounds, position);
- // match point_for_position.as_valid() {
- // Some(point) => {
- // update_go_to_definition_link(
- // editor,
- // Some(GoToDefinitionTrigger::Text(point)),
- // cmd,
- // shift,
- // cx,
- // );
- // hover_at(editor, Some(point), cx);
- // }
- // None => {
- // update_inlay_link_and_hover_points(
- // &position_map.snapshot,
- // point_for_position,
- // editor,
- // cmd,
- // shift,
- // cx,
- // );
- // }
- // }
- // } else {
- // update_go_to_definition_link(editor, None, cmd, shift, cx);
- // hover_at(editor, None, cx);
- // }
+ let text_hovered = text_bounds.contains_point(&event.position);
+ let gutter_hovered = gutter_bounds.contains_point(&event.position);
+ editor.set_gutter_hovered(gutter_hovered, cx);
+
+ // Don't trigger hover popover if mouse is hovering over context menu
+ if text_hovered {
+ let point_for_position = position_map.point_for_position(text_bounds, event.position);
+
+ match point_for_position.as_valid() {
+ Some(point) => {
+ update_go_to_definition_link(
+ editor,
+ Some(GoToDefinitionTrigger::Text(point)),
+ modifiers.command,
+ modifiers.shift,
+ cx,
+ );
+ hover_at(editor, Some(point), cx);
+ }
+ None => {
+ update_inlay_link_and_hover_points(
+ &position_map.snapshot,
+ point_for_position,
+ editor,
+ modifiers.command,
+ modifiers.shift,
+ cx,
+ );
+ }
+ }
- // true
- // }
+ true
+ } else {
+ update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
+ hover_at(editor, None, cx);
+ gutter_hovered
+ }
+ }
fn scroll(
editor: &mut Editor,
@@ -2337,6 +2169,79 @@ impl EditorElement {
// blocks,
// )
// }
+
+ fn paint_mouse_listeners(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ gutter_bounds: Bounds<Pixels>,
+ text_bounds: Bounds<Pixels>,
+ position_map: &Arc<PositionMap>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ cx.on_mouse_event({
+ let position_map = position_map.clone();
+ move |editor, event: &ScrollWheelEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if Self::scroll(editor, event, &position_map, bounds, cx) {
+ cx.stop_propagation();
+ }
+ }
+ });
+ cx.on_mouse_event({
+ let position_map = position_map.clone();
+ move |editor, event: &MouseDownEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
+ cx.stop_propagation()
+ }
+ }
+ });
+ cx.on_mouse_event({
+ let position_map = position_map.clone();
+ move |editor, event: &MouseUpEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if Self::mouse_up(editor, event, &position_map, text_bounds, cx) {
+ cx.stop_propagation()
+ }
+ }
+ });
+ // todo!()
+ // on_down(MouseButton::Right, {
+ // let position_map = position_map.clone();
+ // move |event, editor, cx| {
+ // if !Self::mouse_right_down(
+ // editor,
+ // event.position,
+ // position_map.as_ref(),
+ // text_bounds,
+ // cx,
+ // ) {
+ // cx.propagate_event();
+ // }
+ // }
+ // });
+ cx.on_mouse_event({
+ let position_map = position_map.clone();
+ move |editor, event: &MouseMoveEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
+ cx.stop_propagation()
+ }
+ }
+ });
+ }
}
#[derive(Debug)]
@@ -2556,30 +2461,9 @@ impl Element<Editor> for EditorElement {
let dispatch_context = editor.dispatch_context(cx);
cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
cx.with_key_dispatch_context(dispatch_context, |cx| {
- cx.with_key_listeners(
- [
- build_action_listener(Editor::move_left),
- build_action_listener(Editor::move_right),
- build_action_listener(Editor::move_down),
- build_action_listener(Editor::move_up),
- build_key_listener(
- move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
- if phase == DispatchPhase::Bubble {
- if let KeyMatch::Some(action) = cx.match_keystroke(
- &global_id,
- &key_down.keystroke,
- dispatch_context,
- ) {
- return Some(action);
- }
- }
-
- None
- },
- ),
- ],
- |cx| cx.with_focus(editor.focus_handle.clone(), |_| {}),
- );
+ cx.with_key_listeners(build_key_listeners(global_id), |cx| {
+ cx.with_focus(editor.focus_handle.clone(), |_| {})
+ });
})
});
}
@@ -2609,30 +2493,27 @@ impl Element<Editor> for EditorElement {
cx: &mut gpui::ViewContext<Editor>,
) {
let layout = self.compute_layout(editor, cx, bounds);
+ let gutter_bounds = Bounds {
+ origin: bounds.origin,
+ size: layout.gutter_size,
+ };
+ let text_bounds = Bounds {
+ origin: gutter_bounds.upper_right(),
+ size: layout.text_size,
+ };
- cx.on_mouse_event({
- let position_map = layout.position_map.clone();
- move |editor, event: &ScrollWheelEvent, phase, cx| {
- if phase != DispatchPhase::Bubble {
- return;
- }
-
- if Self::scroll(editor, event, &position_map, bounds, cx) {
- cx.stop_propagation();
- }
- }
- });
+ 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,
- size: layout.gutter_size,
- };
- let text_bounds = Bounds {
- origin: gutter_bounds.upper_right(),
- size: layout.text_size,
- };
-
+ self.paint_mouse_listeners(
+ bounds,
+ gutter_bounds,
+ text_bounds,
+ &layout.position_map,
+ cx,
+ );
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &layout, editor, cx);
@@ -3339,7 +3220,7 @@ impl PositionMap {
let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
- let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into();
+ let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
*exact_unclipped.column_mut() += column_overshoot_after_line_end;
PointForPosition {
previous_valid,
@@ -3661,12 +3542,12 @@ impl HighlightedRange {
// bounds.into_iter()
// }
-pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
- delta.powf(1.5) / 100.0
+pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
+ (delta.pow(1.5) / 100.0).into()
}
-fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
- delta.powf(1.2) / 300.0
+fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
+ (delta.pow(1.2) / 300.0).into()
}
// #[cfg(test)]
@@ -4112,6 +3993,178 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
// }
// }
+fn build_key_listeners(
+ global_element_id: GlobalElementId,
+) -> impl IntoIterator<Item = (TypeId, KeyListener<Editor>)> {
+ [
+ build_action_listener(Editor::move_left),
+ build_action_listener(Editor::move_right),
+ build_action_listener(Editor::move_down),
+ build_action_listener(Editor::move_up),
+ // build_action_listener(Editor::new_file), todo!()
+ // build_action_listener(Editor::new_file_in_direction), todo!()
+ build_action_listener(Editor::cancel),
+ build_action_listener(Editor::newline),
+ build_action_listener(Editor::newline_above),
+ build_action_listener(Editor::newline_below),
+ build_action_listener(Editor::backspace),
+ build_action_listener(Editor::delete),
+ build_action_listener(Editor::tab),
+ build_action_listener(Editor::tab_prev),
+ build_action_listener(Editor::indent),
+ build_action_listener(Editor::outdent),
+ build_action_listener(Editor::delete_line),
+ build_action_listener(Editor::join_lines),
+ build_action_listener(Editor::sort_lines_case_sensitive),
+ build_action_listener(Editor::sort_lines_case_insensitive),
+ build_action_listener(Editor::reverse_lines),
+ build_action_listener(Editor::shuffle_lines),
+ build_action_listener(Editor::convert_to_upper_case),
+ build_action_listener(Editor::convert_to_lower_case),
+ build_action_listener(Editor::convert_to_title_case),
+ build_action_listener(Editor::convert_to_snake_case),
+ build_action_listener(Editor::convert_to_kebab_case),
+ build_action_listener(Editor::convert_to_upper_camel_case),
+ build_action_listener(Editor::convert_to_lower_camel_case),
+ build_action_listener(Editor::delete_to_previous_word_start),
+ build_action_listener(Editor::delete_to_previous_subword_start),
+ build_action_listener(Editor::delete_to_next_word_end),
+ build_action_listener(Editor::delete_to_next_subword_end),
+ build_action_listener(Editor::delete_to_beginning_of_line),
+ build_action_listener(Editor::delete_to_end_of_line),
+ build_action_listener(Editor::cut_to_end_of_line),
+ build_action_listener(Editor::duplicate_line),
+ build_action_listener(Editor::move_line_up),
+ build_action_listener(Editor::move_line_down),
+ build_action_listener(Editor::transpose),
+ build_action_listener(Editor::cut),
+ build_action_listener(Editor::copy),
+ build_action_listener(Editor::paste),
+ build_action_listener(Editor::undo),
+ build_action_listener(Editor::redo),
+ build_action_listener(Editor::move_page_up),
+ build_action_listener(Editor::move_page_down),
+ build_action_listener(Editor::next_screen),
+ build_action_listener(Editor::scroll_cursor_top),
+ build_action_listener(Editor::scroll_cursor_center),
+ build_action_listener(Editor::scroll_cursor_bottom),
+ build_action_listener(|editor, _: &LineDown, cx| {
+ editor.scroll_screen(&ScrollAmount::Line(1.), cx)
+ }),
+ build_action_listener(|editor, _: &LineUp, cx| {
+ editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
+ }),
+ build_action_listener(|editor, _: &HalfPageDown, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
+ }),
+ build_action_listener(|editor, _: &HalfPageUp, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+ }),
+ build_action_listener(|editor, _: &PageDown, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(1.), cx)
+ }),
+ build_action_listener(|editor, _: &PageUp, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
+ }),
+ build_action_listener(Editor::move_to_previous_word_start),
+ build_action_listener(Editor::move_to_previous_subword_start),
+ build_action_listener(Editor::move_to_next_word_end),
+ build_action_listener(Editor::move_to_next_subword_end),
+ build_action_listener(Editor::move_to_beginning_of_line),
+ build_action_listener(Editor::move_to_end_of_line),
+ build_action_listener(Editor::move_to_start_of_paragraph),
+ build_action_listener(Editor::move_to_end_of_paragraph),
+ build_action_listener(Editor::move_to_beginning),
+ build_action_listener(Editor::move_to_end),
+ build_action_listener(Editor::select_up),
+ build_action_listener(Editor::select_down),
+ build_action_listener(Editor::select_left),
+ build_action_listener(Editor::select_right),
+ build_action_listener(Editor::select_to_previous_word_start),
+ build_action_listener(Editor::select_to_previous_subword_start),
+ build_action_listener(Editor::select_to_next_word_end),
+ build_action_listener(Editor::select_to_next_subword_end),
+ build_action_listener(Editor::select_to_beginning_of_line),
+ build_action_listener(Editor::select_to_end_of_line),
+ build_action_listener(Editor::select_to_start_of_paragraph),
+ build_action_listener(Editor::select_to_end_of_paragraph),
+ build_action_listener(Editor::select_to_beginning),
+ build_action_listener(Editor::select_to_end),
+ build_action_listener(Editor::select_all),
+ build_action_listener(|editor, action, cx| {
+ editor.select_all_matches(action, cx).log_err();
+ }),
+ build_action_listener(Editor::select_line),
+ build_action_listener(Editor::split_selection_into_lines),
+ build_action_listener(Editor::add_selection_above),
+ build_action_listener(Editor::add_selection_below),
+ build_action_listener(|editor, action, cx| {
+ editor.select_next(action, cx).log_err();
+ }),
+ build_action_listener(|editor, action, cx| {
+ editor.select_previous(action, cx).log_err();
+ }),
+ build_action_listener(Editor::toggle_comments),
+ build_action_listener(Editor::select_larger_syntax_node),
+ build_action_listener(Editor::select_smaller_syntax_node),
+ build_action_listener(Editor::move_to_enclosing_bracket),
+ build_action_listener(Editor::undo_selection),
+ build_action_listener(Editor::redo_selection),
+ build_action_listener(Editor::go_to_diagnostic),
+ build_action_listener(Editor::go_to_prev_diagnostic),
+ build_action_listener(Editor::go_to_hunk),
+ build_action_listener(Editor::go_to_prev_hunk),
+ build_action_listener(Editor::go_to_definition),
+ build_action_listener(Editor::go_to_definition_split),
+ build_action_listener(Editor::go_to_type_definition),
+ build_action_listener(Editor::go_to_type_definition_split),
+ build_action_listener(Editor::fold),
+ build_action_listener(Editor::fold_at),
+ build_action_listener(Editor::unfold_lines),
+ build_action_listener(Editor::unfold_at),
+ build_action_listener(Editor::fold_selected_ranges),
+ build_action_listener(Editor::show_completions),
+ // build_action_listener(Editor::toggle_code_actions), todo!()
+ // build_action_listener(Editor::open_excerpts), todo!()
+ build_action_listener(Editor::toggle_soft_wrap),
+ build_action_listener(Editor::toggle_inlay_hints),
+ build_action_listener(Editor::reveal_in_finder),
+ build_action_listener(Editor::copy_path),
+ build_action_listener(Editor::copy_relative_path),
+ build_action_listener(Editor::copy_highlight_json),
+ build_action_listener(|editor, action, cx| {
+ editor
+ .format(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ }),
+ build_action_listener(Editor::restart_language_server),
+ build_action_listener(Editor::show_character_palette),
+ // build_action_listener(Editor::confirm_completion), todo!()
+ // build_action_listener(Editor::confirm_code_action), todo!()
+ // build_action_listener(Editor::rename), todo!()
+ // build_action_listener(Editor::confirm_rename), todo!()
+ // build_action_listener(Editor::find_all_references), todo!()
+ build_action_listener(Editor::next_copilot_suggestion),
+ build_action_listener(Editor::previous_copilot_suggestion),
+ build_action_listener(Editor::copilot_suggest),
+ build_key_listener(
+ move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
+ if phase == DispatchPhase::Bubble {
+ if let KeyMatch::Some(action) = cx.match_keystroke(
+ &global_element_id,
+ &key_down.keystroke,
+ dispatch_context,
+ ) {
+ return Some(action);
+ }
+ }
+
+ None
+ },
+ ),
+ ]
+}
+
fn build_key_listener<T: 'static>(
listener: impl Fn(
&mut Editor,
@@ -144,8 +144,7 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
editor.hover_state.info_task = None;
editor.hover_state.triggered_from = None;
- // todo!()
- // editor.clear_background_highlights::<HoverState>(cx);
+ editor.clear_background_highlights::<HoverState>(cx);
if did_hide {
cx.notify();
@@ -325,23 +324,22 @@ fn show_hover(
};
this.update(&mut cx, |this, cx| {
- todo!();
- // if let Some(symbol_range) = hover_popover
- // .as_ref()
- // .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
- // {
- // // Highlight the selected symbol using a background highlight
- // this.highlight_background::<HoverState>(
- // vec![symbol_range],
- // |theme| theme.editor.hover_popover.highlight,
- // cx,
- // );
- // } else {
- // this.clear_background_highlights::<HoverState>(cx);
- // }
- //
- // this.hover_state.info_popover = hover_popover;
- // cx.notify();
+ if let Some(symbol_range) = hover_popover
+ .as_ref()
+ .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
+ {
+ // Highlight the selected symbol using a background highlight
+ this.highlight_background::<HoverState>(
+ vec![symbol_range],
+ |theme| theme.element_hover, // todo! update theme
+ cx,
+ );
+ } else {
+ this.clear_background_highlights::<HoverState>(cx);
+ }
+
+ this.hover_state.info_popover = hover_popover;
+ cx.notify();
})?;
Ok::<_, anyhow::Error>(())
@@ -171,173 +171,170 @@ pub fn update_inlay_link_and_hover_points(
shift_held: bool,
cx: &mut ViewContext<'_, Editor>,
) {
- todo!("old implementation below")
-}
-// ) {
-// let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
-// Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
-// } else {
-// None
-// };
-// let mut go_to_definition_updated = false;
-// let mut hover_updated = false;
-// if let Some(hovered_offset) = hovered_offset {
-// let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
-// let previous_valid_anchor = buffer_snapshot.anchor_at(
-// point_for_position.previous_valid.to_point(snapshot),
-// Bias::Left,
-// );
-// let next_valid_anchor = buffer_snapshot.anchor_at(
-// point_for_position.next_valid.to_point(snapshot),
-// Bias::Right,
-// );
-// if let Some(hovered_hint) = editor
-// .visible_inlay_hints(cx)
-// .into_iter()
-// .skip_while(|hint| {
-// hint.position
-// .cmp(&previous_valid_anchor, &buffer_snapshot)
-// .is_lt()
-// })
-// .take_while(|hint| {
-// hint.position
-// .cmp(&next_valid_anchor, &buffer_snapshot)
-// .is_le()
-// })
-// .max_by_key(|hint| hint.id)
-// {
-// let inlay_hint_cache = editor.inlay_hint_cache();
-// let excerpt_id = previous_valid_anchor.excerpt_id;
-// if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
-// match cached_hint.resolve_state {
-// ResolveState::CanResolve(_, _) => {
-// if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-// inlay_hint_cache.spawn_hint_resolve(
-// buffer_id,
-// excerpt_id,
-// hovered_hint.id,
-// cx,
-// );
-// }
-// }
-// ResolveState::Resolved => {
-// let mut extra_shift_left = 0;
-// let mut extra_shift_right = 0;
-// if cached_hint.padding_left {
-// extra_shift_left += 1;
-// extra_shift_right += 1;
-// }
-// if cached_hint.padding_right {
-// extra_shift_right += 1;
-// }
-// match cached_hint.label {
-// project::InlayHintLabel::String(_) => {
-// if let Some(tooltip) = cached_hint.tooltip {
-// hover_popover::hover_at_inlay(
-// editor,
-// InlayHover {
-// excerpt: excerpt_id,
-// tooltip: match tooltip {
-// InlayHintTooltip::String(text) => HoverBlock {
-// text,
-// kind: HoverBlockKind::PlainText,
-// },
-// InlayHintTooltip::MarkupContent(content) => {
-// HoverBlock {
-// text: content.value,
-// kind: content.kind,
-// }
-// }
-// },
-// range: InlayHighlight {
-// inlay: hovered_hint.id,
-// inlay_position: hovered_hint.position,
-// range: extra_shift_left
-// ..hovered_hint.text.len() + extra_shift_right,
-// },
-// },
-// cx,
-// );
-// hover_updated = true;
-// }
-// }
-// project::InlayHintLabel::LabelParts(label_parts) => {
-// let hint_start =
-// snapshot.anchor_to_inlay_offset(hovered_hint.position);
-// if let Some((hovered_hint_part, part_range)) =
-// hover_popover::find_hovered_hint_part(
-// label_parts,
-// hint_start,
-// hovered_offset,
-// )
-// {
-// let highlight_start =
-// (part_range.start - hint_start).0 + extra_shift_left;
-// let highlight_end =
-// (part_range.end - hint_start).0 + extra_shift_right;
-// let highlight = InlayHighlight {
-// inlay: hovered_hint.id,
-// inlay_position: hovered_hint.position,
-// range: highlight_start..highlight_end,
-// };
-// if let Some(tooltip) = hovered_hint_part.tooltip {
-// hover_popover::hover_at_inlay(
-// editor,
-// InlayHover {
-// excerpt: excerpt_id,
-// tooltip: match tooltip {
-// InlayHintLabelPartTooltip::String(text) => {
-// HoverBlock {
-// text,
-// kind: HoverBlockKind::PlainText,
-// }
-// }
-// InlayHintLabelPartTooltip::MarkupContent(
-// content,
-// ) => HoverBlock {
-// text: content.value,
-// kind: content.kind,
-// },
-// },
-// range: highlight.clone(),
-// },
-// cx,
-// );
-// hover_updated = true;
-// }
-// if let Some((language_server_id, location)) =
-// hovered_hint_part.location
-// {
-// go_to_definition_updated = true;
-// update_go_to_definition_link(
-// editor,
-// Some(GoToDefinitionTrigger::InlayHint(
-// highlight,
-// location,
-// language_server_id,
-// )),
-// cmd_held,
-// shift_held,
-// cx,
-// );
-// }
-// }
-// }
-// };
-// }
-// ResolveState::Resolving => {}
-// }
-// }
-// }
-// }
+ let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
+ Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
+ } else {
+ None
+ };
+ let mut go_to_definition_updated = false;
+ let mut hover_updated = false;
+ if let Some(hovered_offset) = hovered_offset {
+ let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
+ let previous_valid_anchor = buffer_snapshot.anchor_at(
+ point_for_position.previous_valid.to_point(snapshot),
+ Bias::Left,
+ );
+ let next_valid_anchor = buffer_snapshot.anchor_at(
+ point_for_position.next_valid.to_point(snapshot),
+ Bias::Right,
+ );
+ if let Some(hovered_hint) = editor
+ .visible_inlay_hints(cx)
+ .into_iter()
+ .skip_while(|hint| {
+ hint.position
+ .cmp(&previous_valid_anchor, &buffer_snapshot)
+ .is_lt()
+ })
+ .take_while(|hint| {
+ hint.position
+ .cmp(&next_valid_anchor, &buffer_snapshot)
+ .is_le()
+ })
+ .max_by_key(|hint| hint.id)
+ {
+ let inlay_hint_cache = editor.inlay_hint_cache();
+ let excerpt_id = previous_valid_anchor.excerpt_id;
+ if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
+ match cached_hint.resolve_state {
+ ResolveState::CanResolve(_, _) => {
+ if let Some(buffer_id) = previous_valid_anchor.buffer_id {
+ inlay_hint_cache.spawn_hint_resolve(
+ buffer_id,
+ excerpt_id,
+ hovered_hint.id,
+ cx,
+ );
+ }
+ }
+ ResolveState::Resolved => {
+ let mut extra_shift_left = 0;
+ let mut extra_shift_right = 0;
+ if cached_hint.padding_left {
+ extra_shift_left += 1;
+ extra_shift_right += 1;
+ }
+ if cached_hint.padding_right {
+ extra_shift_right += 1;
+ }
+ match cached_hint.label {
+ project::InlayHintLabel::String(_) => {
+ if let Some(tooltip) = cached_hint.tooltip {
+ hover_popover::hover_at_inlay(
+ editor,
+ InlayHover {
+ excerpt: excerpt_id,
+ tooltip: match tooltip {
+ InlayHintTooltip::String(text) => HoverBlock {
+ text,
+ kind: HoverBlockKind::PlainText,
+ },
+ InlayHintTooltip::MarkupContent(content) => {
+ HoverBlock {
+ text: content.value,
+ kind: content.kind,
+ }
+ }
+ },
+ range: InlayHighlight {
+ inlay: hovered_hint.id,
+ inlay_position: hovered_hint.position,
+ range: extra_shift_left
+ ..hovered_hint.text.len() + extra_shift_right,
+ },
+ },
+ cx,
+ );
+ hover_updated = true;
+ }
+ }
+ project::InlayHintLabel::LabelParts(label_parts) => {
+ let hint_start =
+ snapshot.anchor_to_inlay_offset(hovered_hint.position);
+ if let Some((hovered_hint_part, part_range)) =
+ hover_popover::find_hovered_hint_part(
+ label_parts,
+ hint_start,
+ hovered_offset,
+ )
+ {
+ let highlight_start =
+ (part_range.start - hint_start).0 + extra_shift_left;
+ let highlight_end =
+ (part_range.end - hint_start).0 + extra_shift_right;
+ let highlight = InlayHighlight {
+ inlay: hovered_hint.id,
+ inlay_position: hovered_hint.position,
+ range: highlight_start..highlight_end,
+ };
+ if let Some(tooltip) = hovered_hint_part.tooltip {
+ hover_popover::hover_at_inlay(
+ editor,
+ InlayHover {
+ excerpt: excerpt_id,
+ tooltip: match tooltip {
+ InlayHintLabelPartTooltip::String(text) => {
+ HoverBlock {
+ text,
+ kind: HoverBlockKind::PlainText,
+ }
+ }
+ InlayHintLabelPartTooltip::MarkupContent(
+ content,
+ ) => HoverBlock {
+ text: content.value,
+ kind: content.kind,
+ },
+ },
+ range: highlight.clone(),
+ },
+ cx,
+ );
+ hover_updated = true;
+ }
+ if let Some((language_server_id, location)) =
+ hovered_hint_part.location
+ {
+ go_to_definition_updated = true;
+ update_go_to_definition_link(
+ editor,
+ Some(GoToDefinitionTrigger::InlayHint(
+ highlight,
+ location,
+ language_server_id,
+ )),
+ cmd_held,
+ shift_held,
+ cx,
+ );
+ }
+ }
+ }
+ };
+ }
+ ResolveState::Resolving => {}
+ }
+ }
+ }
+ }
-// if !go_to_definition_updated {
-// update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
-// }
-// if !hover_updated {
-// hover_popover::hover_at(editor, None, cx);
-// }
-// }
+ if !go_to_definition_updated {
+ update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
+ }
+ if !hover_updated {
+ hover_popover::hover_at(editor, None, cx);
+ }
+}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinkDefinitionKind {
@@ -288,16 +288,15 @@ impl ScrollManager {
}
}
-// todo!()
impl Editor {
- // pub fn vertical_scroll_margin(&mut self) -> usize {
- // self.scroll_manager.vertical_scroll_margin as usize
- // }
+ pub fn vertical_scroll_margin(&mut self) -> usize {
+ self.scroll_manager.vertical_scroll_margin as usize
+ }
- // pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
- // self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
- // cx.notify();
- // }
+ pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
+ self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
+ cx.notify();
+ }
pub fn visible_line_count(&self) -> Option<f32> {
self.scroll_manager.visible_line_count
@@ -349,11 +348,9 @@ impl Editor {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
}
- pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> {
+ pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
- // todo!() Should `self.scroll_manager.anchor.scroll_position()` return `Pixels`?
- // self.scroll_manager.anchor.scroll_position(&display_map)
- todo!()
+ self.scroll_manager.anchor.scroll_position(&display_map)
}
pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
@@ -382,50 +379,50 @@ impl Editor {
.set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
}
- // pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
- // if matches!(self.mode, EditorMode::SingleLine) {
- // cx.propagate_action();
- // return;
- // }
-
- // if self.take_rename(true, cx).is_some() {
- // return;
- // }
-
- // let cur_position = self.scroll_position(cx);
- // let new_pos = cur_position + point(0., amount.lines(self));
- // self.set_scroll_position(new_pos, cx);
- // }
-
- // /// Returns an ordering. The newest selection is:
- // /// Ordering::Equal => on screen
- // /// Ordering::Less => above the screen
- // /// Ordering::Greater => below the screen
- // pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
- // let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
- // let newest_head = self
- // .selections
- // .newest_anchor()
- // .head()
- // .to_display_point(&snapshot);
- // let screen_top = self
- // .scroll_manager
- // .anchor
- // .anchor
- // .to_display_point(&snapshot);
-
- // if screen_top > newest_head {
- // return Ordering::Less;
- // }
-
- // if let Some(visible_lines) = self.visible_line_count() {
- // if newest_head.row() < screen_top.row() + visible_lines as u32 {
- // return Ordering::Equal;
- // }
- // }
-
- // Ordering::Greater
- // }
+ pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
+ if matches!(self.mode, EditorMode::SingleLine) {
+ cx.propagate();
+ return;
+ }
+
+ if self.take_rename(true, cx).is_some() {
+ return;
+ }
+
+ let cur_position = self.scroll_position(cx);
+ let new_pos = cur_position + point(0., amount.lines(self));
+ self.set_scroll_position(new_pos, cx);
+ }
+
+ /// Returns an ordering. The newest selection is:
+ /// Ordering::Equal => on screen
+ /// Ordering::Less => above the screen
+ /// Ordering::Greater => below the screen
+ pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
+ let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let newest_head = self
+ .selections
+ .newest_anchor()
+ .head()
+ .to_display_point(&snapshot);
+ let screen_top = self
+ .scroll_manager
+ .anchor
+ .anchor
+ .to_display_point(&snapshot);
+
+ if screen_top > newest_head {
+ return Ordering::Less;
+ }
+
+ if let Some(visible_lines) = self.visible_line_count() {
+ if newest_head.row() < screen_top.row() + visible_lines as u32 {
+ return Ordering::Equal;
+ }
+ }
+
+ Ordering::Greater
+ }
pub fn read_scroll_position_from_db(
&mut self,
@@ -1,66 +1,27 @@
use super::Axis;
-use crate::Editor;
-use gpui::{AppContext, Point, ViewContext};
-
-// actions!(
-// editor,
-// [
-// LineDown,
-// LineUp,
-// HalfPageDown,
-// HalfPageUp,
-// PageDown,
-// PageUp,
-// NextScreen,
-// ScrollCursorTop,
-// ScrollCursorCenter,
-// ScrollCursorBottom,
-// ]
-// );
-
-pub fn init(cx: &mut AppContext) {
- // todo!()
- // cx.add_action(Editor::next_screen);
- // cx.add_action(Editor::scroll_cursor_top);
- // cx.add_action(Editor::scroll_cursor_center);
- // cx.add_action(Editor::scroll_cursor_bottom);
- // cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
- // this.scroll_screen(&ScrollAmount::Line(1.), cx)
- // });
- // cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
- // this.scroll_screen(&ScrollAmount::Line(-1.), cx)
- // });
- // cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
- // this.scroll_screen(&ScrollAmount::Page(0.5), cx)
- // });
- // cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
- // this.scroll_screen(&ScrollAmount::Page(-0.5), cx)
- // });
- // cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
- // this.scroll_screen(&ScrollAmount::Page(1.), cx)
- // });
- // cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
- // this.scroll_screen(&ScrollAmount::Page(-1.), cx)
- // });
-}
+use crate::{
+ Autoscroll, Bias, Editor, EditorMode, NextScreen, ScrollAnchor, ScrollCursorBottom,
+ ScrollCursorCenter, ScrollCursorTop,
+};
+use gpui::{actions, AppContext, Point, ViewContext};
impl Editor {
- // pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) -> Option<()> {
- // if self.take_rename(true, cx).is_some() {
- // return None;
- // }
+ pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
+ if self.take_rename(true, cx).is_some() {
+ return;
+ }
- // if self.mouse_context_menu.read(cx).visible() {
- // return None;
- // }
+ // todo!()
+ // if self.mouse_context_menu.read(cx).visible() {
+ // return None;
+ // }
- // if matches!(self.mode, EditorMode::SingleLine) {
- // cx.propagate_action();
- // return None;
- // }
- // self.request_autoscroll(Autoscroll::Next, cx);
- // Some(())
- // }
+ if matches!(self.mode, EditorMode::SingleLine) {
+ cx.propagate();
+ return;
+ }
+ self.request_autoscroll(Autoscroll::Next, cx);
+ }
pub fn scroll(
&mut self,
@@ -72,79 +33,71 @@ impl Editor {
self.set_scroll_position(scroll_position, cx);
}
- // fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
- // let snapshot = editor.snapshot(cx).display_snapshot;
- // let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
+ pub fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
+ let snapshot = self.snapshot(cx).display_snapshot;
+ let scroll_margin_rows = self.vertical_scroll_margin() as u32;
- // let mut new_screen_top = editor.selections.newest_display(cx).head();
- // *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
- // *new_screen_top.column_mut() = 0;
- // let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
- // let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
+ let mut new_screen_top = self.selections.newest_display(cx).head();
+ *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
+ *new_screen_top.column_mut() = 0;
+ let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
+ let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
- // editor.set_scroll_anchor(
- // ScrollAnchor {
- // anchor: new_anchor,
- // offset: Default::default(),
- // },
- // cx,
- // )
- // }
+ self.set_scroll_anchor(
+ ScrollAnchor {
+ anchor: new_anchor,
+ offset: Default::default(),
+ },
+ cx,
+ )
+ }
- // fn scroll_cursor_center(
- // editor: &mut Editor,
- // _: &ScrollCursorCenter,
- // cx: &mut ViewContext<Editor>,
- // ) {
- // let snapshot = editor.snapshot(cx).display_snapshot;
- // let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
- // visible_rows as u32
- // } else {
- // return;
- // };
+ pub fn scroll_cursor_center(&mut self, _: &ScrollCursorCenter, cx: &mut ViewContext<Editor>) {
+ let snapshot = self.snapshot(cx).display_snapshot;
+ let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
+ visible_rows as u32
+ } else {
+ return;
+ };
- // let mut new_screen_top = editor.selections.newest_display(cx).head();
- // *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
- // *new_screen_top.column_mut() = 0;
- // let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
- // let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
+ let mut new_screen_top = self.selections.newest_display(cx).head();
+ *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
+ *new_screen_top.column_mut() = 0;
+ let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
+ let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
- // editor.set_scroll_anchor(
- // ScrollAnchor {
- // anchor: new_anchor,
- // offset: Default::default(),
- // },
- // cx,
- // )
- // }
+ self.set_scroll_anchor(
+ ScrollAnchor {
+ anchor: new_anchor,
+ offset: Default::default(),
+ },
+ cx,
+ )
+ }
- // fn scroll_cursor_bottom(
- // editor: &mut Editor,
- // _: &ScrollCursorBottom,
- // cx: &mut ViewContext<Editor>,
- // ) {
- // let snapshot = editor.snapshot(cx).display_snapshot;
- // let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
- // let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
- // visible_rows as u32
- // } else {
- // return;
- // };
+ pub fn scroll_cursor_bottom(&mut self, _: &ScrollCursorBottom, cx: &mut ViewContext<Editor>) {
+ let snapshot = self.snapshot(cx).display_snapshot;
+ let scroll_margin_rows = self.vertical_scroll_margin() as u32;
+ let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
+ visible_rows as u32
+ } else {
+ return;
+ };
- // let mut new_screen_top = editor.selections.newest_display(cx).head();
- // *new_screen_top.row_mut() = new_screen_top
- // .row()
- // .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
- // *new_screen_top.column_mut() = 0;
- // let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
- // let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
+ let mut new_screen_top = self.selections.newest_display(cx).head();
+ *new_screen_top.row_mut() = new_screen_top
+ .row()
+ .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
+ *new_screen_top.column_mut() = 0;
+ let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
+ let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
- // editor.set_scroll_anchor(
- // ScrollAnchor {
- // anchor: new_anchor,
- // offset: Default::default(),
- // },
- // cx,
- // )
- // }
+ self.set_scroll_anchor(
+ ScrollAnchor {
+ anchor: new_anchor,
+ offset: Default::default(),
+ },
+ cx,
+ )
+ }
}
@@ -302,39 +302,39 @@ impl SelectionsCollection {
.collect()
}
- // pub fn build_columnar_selection(
- // &mut self,
- // display_map: &DisplaySnapshot,
- // row: u32,
- // positions: &Range<Pixels>,
- // reversed: bool,
- // text_layout_details: &TextLayoutDetails,
- // ) -> Option<Selection<Point>> {
- // let is_empty = positions.start == positions.end;
- // let line_len = display_map.line_len(row);
-
- // let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
-
- // let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
- // if start_col < line_len || (is_empty && positions.start == layed_out_line.width()) {
- // let start = DisplayPoint::new(row, start_col);
- // let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
- // let end = DisplayPoint::new(row, end_col);
-
- // Some(Selection {
- // id: post_inc(&mut self.next_selection_id),
- // start: start.to_point(display_map),
- // end: end.to_point(display_map),
- // reversed,
- // goal: SelectionGoal::HorizontalRange {
- // start: positions.start,
- // end: positions.end,
- // },
- // })
- // } else {
- // None
- // }
- // }
+ pub fn build_columnar_selection(
+ &mut self,
+ display_map: &DisplaySnapshot,
+ row: u32,
+ positions: &Range<Pixels>,
+ reversed: bool,
+ text_layout_details: &TextLayoutDetails,
+ ) -> Option<Selection<Point>> {
+ let is_empty = positions.start == positions.end;
+ let line_len = display_map.line_len(row);
+
+ let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
+
+ let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
+ if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
+ let start = DisplayPoint::new(row, start_col);
+ let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
+ let end = DisplayPoint::new(row, end_col);
+
+ Some(Selection {
+ id: post_inc(&mut self.next_selection_id),
+ start: start.to_point(display_map),
+ end: end.to_point(display_map),
+ reversed,
+ goal: SelectionGoal::HorizontalRange {
+ start: positions.start.into(),
+ end: positions.end.into(),
+ },
+ })
+ } else {
+ None
+ }
+ }
pub(crate) fn change_with<R>(
&mut self,
@@ -12,7 +12,6 @@ use workspace::ModalRegistry;
actions!(Toggle, Cancel, Confirm);
pub fn init(cx: &mut AppContext) {
- cx.register_action_type::<Toggle>();
cx.global_mut::<ModalRegistry>()
.register_modal(Toggle, |workspace, cx| {
let editor = workspace
@@ -1,9 +1,54 @@
use crate::SharedString;
use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet};
+use lazy_static::lazy_static;
+use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
use std::any::{type_name, Any};
+/// Actions are used to implement keyboard-driven UI.
+/// When you declare an action, you can bind keys to the action in the keymap and
+/// listeners for that action in the element tree.
+///
+/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
+/// action for each listed action name.
+/// ```rust
+/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
+/// ```
+/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
+/// it will automatically
+/// ```
+/// #[action]
+/// pub struct SelectNext {
+/// pub replace_newest: bool,
+/// }
+///
+/// Any type A that satisfies the following bounds is automatically an action:
+///
+/// ```
+/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
+/// ```
+///
+/// The `#[action]` annotation will derive these implementations for your struct automatically. If you
+/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only
+/// generates the code needed to register your action before `main`. Then you'll need to implement all
+/// the traits manually.
+///
+/// ```
+/// #[gpui::register_action]
+/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
+/// pub struct Paste {
+/// pub content: SharedString,
+/// }
+///
+/// impl std::default::Default for Paste {
+/// fn default() -> Self {
+/// Self {
+/// content: SharedString::from("🍝"),
+/// }
+/// }
+/// }
+/// ```
pub trait Action: std::fmt::Debug + 'static {
fn qualified_name() -> SharedString
where
@@ -17,32 +62,7 @@ pub trait Action: std::fmt::Debug + 'static {
fn as_any(&self) -> &dyn Any;
}
-// actions defines structs that can be used as actions.
-#[macro_export]
-macro_rules! actions {
- () => {};
-
- ( $name:ident ) => {
- #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
- pub struct $name;
- };
-
- ( $name:ident { $($token:tt)* } ) => {
- #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
- pub struct $name { $($token)* }
- };
-
- ( $name:ident, $($rest:tt)* ) => {
- actions!($name);
- actions!($($rest)*);
- };
-
- ( $name:ident { $($token:tt)* }, $($rest:tt)* ) => {
- actions!($name { $($token)* });
- actions!($($rest)*);
- };
-}
-
+// Types become actions by satisfying a list of trait bounds.
impl<A> Action for A
where
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
@@ -80,6 +100,61 @@ where
}
}
+type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
+
+lazy_static! {
+ static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
+}
+
+#[derive(Default)]
+struct ActionRegistry {
+ builders_by_name: HashMap<SharedString, ActionBuilder>,
+ all_names: Vec<SharedString>, // So we can return a static slice.
+}
+
+/// Register an action type to allow it to be referenced in keymaps.
+pub fn register_action<A: Action>() {
+ let name = A::qualified_name();
+ let mut lock = ACTION_REGISTRY.write();
+ lock.builders_by_name.insert(name.clone(), A::build);
+ lock.all_names.push(name);
+}
+
+/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
+pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
+ let lock = ACTION_REGISTRY.read();
+ let build_action = lock
+ .builders_by_name
+ .get(name)
+ .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
+ (build_action)(params)
+}
+
+pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
+ let lock = ACTION_REGISTRY.read();
+ RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
+ registry.all_names.as_slice()
+ })
+}
+
+/// Defines unit structs that can be used as actions.
+/// To use more complex data types as actions, annotate your type with the #[action] macro.
+#[macro_export]
+macro_rules! actions {
+ () => {};
+
+ ( $name:ident ) => {
+ #[gpui::register_action]
+ #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
+ pub struct $name;
+ };
+
+ ( $name:ident, $($rest:tt)* ) => {
+ actions!($name);
+ actions!($($rest)*);
+ };
+}
+
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DispatchContext {
set: HashSet<SharedString>,
@@ -317,22 +392,23 @@ fn skip_whitespace(source: &str) -> &str {
#[cfg(test)]
mod tests {
use super::*;
+ use crate as gpui;
use DispatchContextPredicate::*;
#[test]
fn test_actions_definition() {
{
- actions!(A, B { field: i32 }, C, D, E, F {}, G);
+ actions!(A, B, C, D, E, F, G);
}
{
actions!(
A,
- B { field: i32 },
+ B,
C,
D,
E,
- F {},
+ F,
G, // Don't wrap, test the trailing comma
);
}
@@ -17,9 +17,9 @@ use crate::{
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
- PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString,
- SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
- View, Window, WindowContext, WindowHandle, WindowId,
+ PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
+ Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
+ WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
@@ -140,7 +140,6 @@ impl App {
}
}
-type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
@@ -154,7 +153,7 @@ type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
// }
pub struct AppContext {
- this: Weak<AppCell>,
+ pub(crate) this: Weak<AppCell>,
pub(crate) platform: Rc<dyn Platform>,
app_metadata: AppMetadata,
text_system: Arc<TextSystem>,
@@ -176,7 +175,6 @@ pub struct AppContext {
pub(crate) keymap: Arc<Mutex<Keymap>>,
pub(crate) global_action_listeners:
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
- action_builders: HashMap<SharedString, ActionBuilder>,
pending_effects: VecDeque<Effect>,
pub(crate) pending_notifications: HashSet<EntityId>,
pub(crate) pending_global_notifications: HashSet<TypeId>,
@@ -234,7 +232,6 @@ impl AppContext {
windows: SlotMap::with_key(),
keymap: Arc::new(Mutex::new(Keymap::default())),
global_action_listeners: HashMap::default(),
- action_builders: HashMap::default(),
pending_effects: VecDeque::new(),
pending_notifications: HashSet::default(),
pending_global_notifications: HashSet::default(),
@@ -522,7 +519,7 @@ impl AppContext {
window_handle
.update(self, |_, cx| {
if cx.window.focus == focused {
- let mut listeners = mem::take(&mut cx.window.focus_listeners);
+ let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners);
let focused = focused
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
let blurred = cx
@@ -538,8 +535,8 @@ impl AppContext {
}
}
- listeners.extend(cx.window.focus_listeners.drain(..));
- cx.window.focus_listeners = listeners;
+ listeners.extend(cx.window.current_frame.focus_listeners.drain(..));
+ cx.window.current_frame.focus_listeners = listeners;
}
})
.ok();
@@ -695,10 +692,6 @@ impl AppContext {
)
}
- pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
- self.action_builders.keys().cloned()
- }
-
/// Move the global of the given type to the stack.
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
GlobalLease::new(
@@ -761,24 +754,6 @@ impl AppContext {
}));
}
- /// Register an action type to allow it to be referenced in keymaps.
- pub fn register_action_type<A: Action>(&mut self) {
- self.action_builders.insert(A::qualified_name(), A::build);
- }
-
- /// Construct an action based on its name and parameters.
- pub fn build_action(
- &mut self,
- name: &str,
- params: Option<serde_json::Value>,
- ) -> Result<Box<dyn Action>> {
- let build = self
- .action_builders
- .get(name)
- .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
- (build)(params)
- }
-
/// Event handlers propagate events by default. Call this method to stop dispatching to
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
/// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
@@ -13,6 +13,7 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak,
},
+ thread::panicking,
};
slotmap::new_key_type! { pub struct EntityId; }
@@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
impl<'a, T> Drop for Lease<'a, T> {
fn drop(&mut self) {
- if self.entity.is_some() {
- // We don't panic here, because other panics can cause us to drop the lease without ending it cleanly.
- log::error!("Leases must be ended with EntityMap::end_lease")
+ if self.entity.is_some() && !panicking() {
+ panic!("Leases must be ended with EntityMap::end_lease")
}
}
}
@@ -203,6 +203,15 @@ pub fn red() -> Hsla {
}
}
+pub fn blue() -> Hsla {
+ Hsla {
+ h: 0.6,
+ s: 1.,
+ l: 0.5,
+ a: 1.,
+ }
+}
+
impl Hsla {
/// Returns true if the HSLA color is fully transparent, false otherwise.
pub fn is_transparent(&self) -> bool {
@@ -2,8 +2,10 @@ mod div;
mod img;
mod svg;
mod text;
+mod uniform_list;
pub use div::*;
pub use img::*;
pub use svg::*;
pub use text::*;
+pub use uniform_list::*;
@@ -1,28 +1,28 @@
use crate::{
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
- ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
+ ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
- Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
- StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility,
+ Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
+ StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
};
use refineable::Refineable;
use smallvec::SmallVec;
pub struct Div<
V: 'static,
- I: ElementInteraction<V> = StatelessInteraction<V>,
+ I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled,
> {
- interaction: I,
+ interactivity: I,
focus: F,
children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>,
base_style: StyleRefinement,
}
-pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
+pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
Div {
- interaction: StatelessInteraction::default(),
+ interactivity: StatelessInteractivity::default(),
focus: FocusDisabled,
children: SmallVec::new(),
group: None,
@@ -30,14 +30,14 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
}
}
-impl<V, F> Div<V, StatelessInteraction<V>, F>
+impl<V, F> Div<V, StatelessInteractivity<V>, F>
where
V: 'static,
F: ElementFocus<V>,
{
- pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteraction<V>, F> {
+ pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
Div {
- interaction: id.into().into(),
+ interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
focus: self.focus,
children: self.children,
group: self.group,
@@ -48,7 +48,7 @@ where
impl<V, I, F> Div<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
@@ -98,16 +98,20 @@ where
let mut computed_style = Style::default();
computed_style.refine(&self.base_style);
self.focus.refine_style(&mut computed_style, cx);
- self.interaction
- .refine_style(&mut computed_style, bounds, &element_state.interactive, cx);
+ self.interactivity.refine_style(
+ &mut computed_style,
+ bounds,
+ &element_state.interactive,
+ cx,
+ );
computed_style
}
}
-impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
- pub fn focusable(self) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
+impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
+ pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div {
- interaction: self.interaction,
+ interactivity: self.interactivity,
focus: FocusEnabled::new(),
children: self.children,
group: self.group,
@@ -118,9 +122,9 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
pub fn track_focus(
self,
handle: &FocusHandle,
- ) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
+ ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div {
- interaction: self.interaction,
+ interactivity: self.interactivity,
focus: FocusEnabled::tracked(handle),
children: self.children,
group: self.group,
@@ -145,13 +149,13 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
}
}
-impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
+impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
pub fn track_focus(
self,
handle: &FocusHandle,
- ) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
+ ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div {
- interaction: self.interaction.into_stateful(handle),
+ interactivity: self.interactivity.into_stateful(handle),
focus: handle.clone().into(),
children: self.children,
group: self.group,
@@ -163,7 +167,7 @@ impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
where
V: 'static,
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
&mut self.focus.focus_listeners
@@ -191,13 +195,13 @@ pub struct DivState {
impl<V, I, F> Element<V> for Div<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
type ElementState = DivState;
fn id(&self) -> Option<ElementId> {
- self.interaction
+ self.interactivity
.as_stateful()
.map(|identified| identified.id.clone())
}
@@ -209,7 +213,7 @@ where
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
- self.interaction.initialize(cx, |cx| {
+ self.interactivity.initialize(cx, |cx| {
self.focus
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
element_state.focus_handle = focus_handle;
@@ -281,11 +285,11 @@ where
(child_max - child_min).into()
};
- cx.stack(z_index, |cx| {
- cx.stack(0, |cx| {
+ cx.with_z_index(z_index, |cx| {
+ cx.with_z_index(0, |cx| {
style.paint(bounds, cx);
this.focus.paint(bounds, cx);
- this.interaction.paint(
+ this.interactivity.paint(
bounds,
content_size,
style.overflow,
@@ -293,7 +297,7 @@ where
cx,
);
});
- cx.stack(1, |cx| {
+ cx.with_z_index(1, |cx| {
style.apply_text_style(cx, |cx| {
style.apply_overflow(bounds, cx, |cx| {
let scroll_offset = element_state.interactive.scroll_offset();
@@ -316,7 +320,7 @@ where
impl<V, I, F> Component<V> for Div<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn render(self) -> AnyElement<V> {
@@ -326,7 +330,7 @@ where
impl<V, I, F> ParentElement<V> for Div<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
@@ -336,7 +340,7 @@ where
impl<V, I, F> Styled for Div<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
@@ -346,19 +350,19 @@ where
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
- fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
- self.interaction.as_stateless_mut()
+ fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+ self.interactivity.as_stateless_mut()
}
}
-impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteraction<V>, F>
+impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
where
F: ElementFocus<V>,
{
- fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
- &mut self.interaction
+ fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+ &mut self.interactivity
}
}
@@ -1,15 +1,15 @@
use crate::{
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
- ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
- LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
- StatelessInteractive, StyleRefinement, Styled, ViewContext,
+ ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
+ LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
+ StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use futures::FutureExt;
use util::ResultExt;
pub struct Img<
V: 'static,
- I: ElementInteraction<V> = StatelessInteraction<V>,
+ I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled,
> {
base: Div<V, I, F>,
@@ -17,7 +17,7 @@ pub struct Img<
grayscale: bool,
}
-pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
+pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
Img {
base: div(),
uri: None,
@@ -28,7 +28,7 @@ pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I, F> Img<V, I, F>
where
V: 'static,
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
@@ -42,11 +42,11 @@ where
}
}
-impl<V, F> Img<V, StatelessInteraction<V>, F>
+impl<V, F> Img<V, StatelessInteractivity<V>, F>
where
F: ElementFocus<V>,
{
- pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteraction<V>, F> {
+ pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
Img {
base: self.base.id(id),
uri: self.uri,
@@ -57,7 +57,7 @@ where
impl<V, I, F> Component<V> for Img<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn render(self) -> AnyElement<V> {
@@ -67,7 +67,7 @@ where
impl<V, I, F> Element<V> for Img<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
type ElementState = DivState;
@@ -101,7 +101,7 @@ where
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) {
- cx.stack(0, |cx| {
+ cx.with_z_index(0, |cx| {
self.base.paint(bounds, view, element_state, cx);
});
@@ -118,7 +118,7 @@ where
.and_then(ResultExt::log_err)
{
let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
- cx.stack(1, |cx| {
+ cx.with_z_index(1, |cx| {
cx.paint_image(bounds, corner_radii, data, self.grayscale)
.log_err()
});
@@ -136,7 +136,7 @@ where
impl<V, I, F> Styled for Img<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
@@ -146,27 +146,27 @@ where
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
- fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
- self.base.stateless_interaction()
+ fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+ self.base.stateless_interactivity()
}
}
-impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteraction<V>, F>
+impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
where
F: ElementFocus<V>,
{
- fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
- self.base.stateful_interaction()
+ fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+ self.base.stateful_interactivity()
}
}
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
where
V: 'static,
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners()
@@ -1,21 +1,21 @@
use crate::{
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
- ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
- SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
- StatelessInteractive, StyleRefinement, Styled, ViewContext,
+ ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
+ SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
+ StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use util::ResultExt;
pub struct Svg<
V: 'static,
- I: ElementInteraction<V> = StatelessInteraction<V>,
+ I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled,
> {
base: Div<V, I, F>,
path: Option<SharedString>,
}
-pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
+pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
Svg {
base: div(),
path: None,
@@ -24,7 +24,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I, F> Svg<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
@@ -33,11 +33,11 @@ where
}
}
-impl<V, F> Svg<V, StatelessInteraction<V>, F>
+impl<V, F> Svg<V, StatelessInteractivity<V>, F>
where
F: ElementFocus<V>,
{
- pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteraction<V>, F> {
+ pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
Svg {
base: self.base.id(id),
path: self.path,
@@ -47,7 +47,7 @@ where
impl<V, I, F> Component<V> for Svg<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn render(self) -> AnyElement<V> {
@@ -57,7 +57,7 @@ where
impl<V, I, F> Element<V> for Svg<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
type ElementState = DivState;
@@ -107,7 +107,7 @@ where
impl<V, I, F> Styled for Svg<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
@@ -117,27 +117,27 @@ where
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
F: ElementFocus<V>,
{
- fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
- self.base.stateless_interaction()
+ fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+ self.base.stateless_interactivity()
}
}
-impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteraction<V>, F>
+impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
where
V: 'static,
F: ElementFocus<V>,
{
- fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
- self.base.stateful_interaction()
+ fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+ self.base.stateful_interactivity()
}
}
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
where
- I: ElementInteraction<V>,
+ I: ElementInteractivity<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners()
@@ -127,6 +127,7 @@ impl<V: 'static> Element<V> for Text<V> {
let element_state = element_state
.as_ref()
.expect("measurement has not been performed");
+
let line_height = element_state.line_height;
let mut line_origin = bounds.origin;
for line in &element_state.lines {
@@ -0,0 +1,244 @@
+use crate::{
+ point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
+ ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
+ StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
+ StyleRefinement, Styled, ViewContext,
+};
+use parking_lot::Mutex;
+use smallvec::SmallVec;
+use std::{cmp, ops::Range, sync::Arc};
+use taffy::style::Overflow;
+
+pub fn uniform_list<Id, V, C>(
+ id: Id,
+ item_count: usize,
+ f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
+) -> UniformList<V>
+where
+ Id: Into<ElementId>,
+ V: 'static,
+ C: Component<V>,
+{
+ let id = id.into();
+ UniformList {
+ id: id.clone(),
+ style: Default::default(),
+ item_count,
+ render_items: Box::new(move |view, visible_range, cx| {
+ f(view, visible_range, cx)
+ .into_iter()
+ .map(|component| component.render())
+ .collect()
+ }),
+ interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()),
+ scroll_handle: None,
+ }
+}
+
+pub struct UniformList<V: 'static> {
+ id: ElementId,
+ style: StyleRefinement,
+ item_count: usize,
+ render_items: Box<
+ dyn for<'a> Fn(
+ &'a mut V,
+ Range<usize>,
+ &'a mut ViewContext<V>,
+ ) -> SmallVec<[AnyElement<V>; 64]>,
+ >,
+ interactivity: StatefulInteractivity<V>,
+ scroll_handle: Option<UniformListScrollHandle>,
+}
+
+#[derive(Clone)]
+pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
+
+#[derive(Clone, Debug)]
+struct ScrollHandleState {
+ item_height: Pixels,
+ list_height: Pixels,
+ scroll_offset: Arc<Mutex<Point<Pixels>>>,
+}
+
+impl UniformListScrollHandle {
+ pub fn new() -> Self {
+ Self(Arc::new(Mutex::new(None)))
+ }
+
+ pub fn scroll_to_item(&self, ix: usize) {
+ if let Some(state) = &*self.0.lock() {
+ let mut scroll_offset = state.scroll_offset.lock();
+ let item_top = state.item_height * ix;
+ let item_bottom = item_top + state.item_height;
+ let scroll_top = -scroll_offset.y;
+ if item_top < scroll_top {
+ scroll_offset.y = -item_top;
+ } else if item_bottom > scroll_top + state.list_height {
+ scroll_offset.y = -(item_bottom - state.list_height);
+ }
+ }
+ }
+}
+
+impl<V: 'static> Styled for UniformList<V> {
+ fn style(&mut self) -> &mut StyleRefinement {
+ &mut self.style
+ }
+}
+
+impl<V: 'static> Element<V> for UniformList<V> {
+ type ElementState = InteractiveElementState;
+
+ fn id(&self) -> Option<crate::ElementId> {
+ Some(self.id.clone())
+ }
+
+ fn initialize(
+ &mut self,
+ _: &mut V,
+ element_state: Option<Self::ElementState>,
+ _: &mut ViewContext<V>,
+ ) -> Self::ElementState {
+ element_state.unwrap_or_default()
+ }
+
+ fn layout(
+ &mut self,
+ _view_state: &mut V,
+ _element_state: &mut Self::ElementState,
+ cx: &mut ViewContext<V>,
+ ) -> LayoutId {
+ cx.request_layout(&self.computed_style(), None)
+ }
+
+ fn paint(
+ &mut self,
+ bounds: crate::Bounds<crate::Pixels>,
+ view_state: &mut V,
+ element_state: &mut Self::ElementState,
+ cx: &mut ViewContext<V>,
+ ) {
+ let style = self.computed_style();
+ style.paint(bounds, cx);
+
+ let border = style.border_widths.to_pixels(cx.rem_size());
+ let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
+
+ let padded_bounds = Bounds::from_corners(
+ bounds.origin + point(border.left + padding.left, border.top + padding.top),
+ bounds.lower_right()
+ - point(border.right + padding.right, border.bottom + padding.bottom),
+ );
+
+ cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
+ let content_size;
+ if self.item_count > 0 {
+ let item_height = self.measure_item_height(view_state, padded_bounds, cx);
+ if let Some(scroll_handle) = self.scroll_handle.clone() {
+ scroll_handle.0.lock().replace(ScrollHandleState {
+ item_height,
+ list_height: padded_bounds.size.height,
+ scroll_offset: element_state.track_scroll_offset(),
+ });
+ }
+ let visible_item_count = if item_height > px(0.) {
+ (padded_bounds.size.height / item_height).ceil() as usize + 1
+ } else {
+ 0
+ };
+ let scroll_offset = element_state
+ .scroll_offset()
+ .map_or((0.0).into(), |offset| offset.y);
+ let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
+ let visible_range = first_visible_element_ix
+ ..cmp::min(
+ first_visible_element_ix + visible_item_count,
+ self.item_count,
+ );
+
+ let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
+
+ content_size = Size {
+ width: padded_bounds.size.width,
+ height: item_height * self.item_count,
+ };
+
+ cx.with_z_index(1, |cx| {
+ for (item, ix) in items.iter_mut().zip(visible_range) {
+ item.initialize(view_state, cx);
+
+ let layout_id = item.layout(view_state, cx);
+ cx.compute_layout(
+ layout_id,
+ Size {
+ width: AvailableSpace::Definite(bounds.size.width),
+ height: AvailableSpace::Definite(item_height),
+ },
+ );
+ let offset =
+ padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
+ cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
+ }
+ });
+ } else {
+ content_size = Size {
+ width: bounds.size.width,
+ height: px(0.),
+ };
+ }
+
+ let overflow = point(style.overflow.x, Overflow::Scroll);
+
+ cx.with_z_index(0, |cx| {
+ self.interactivity
+ .paint(bounds, content_size, overflow, element_state, cx);
+ });
+ })
+ }
+}
+
+impl<V> UniformList<V> {
+ fn measure_item_height(
+ &self,
+ view_state: &mut V,
+ list_bounds: Bounds<Pixels>,
+ cx: &mut ViewContext<V>,
+ ) -> Pixels {
+ let mut items = (self.render_items)(view_state, 0..1, cx);
+ debug_assert!(items.len() == 1);
+ let mut item_to_measure = items.pop().unwrap();
+ item_to_measure.initialize(view_state, cx);
+ let layout_id = item_to_measure.layout(view_state, cx);
+ cx.compute_layout(
+ layout_id,
+ Size {
+ width: AvailableSpace::Definite(list_bounds.size.width),
+ height: AvailableSpace::MinContent,
+ },
+ );
+ cx.layout_bounds(layout_id).size.height
+ }
+
+ pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
+ self.scroll_handle = Some(handle);
+ self
+ }
+}
+
+impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
+ fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
+ self.interactivity.as_stateless_mut()
+ }
+}
+
+impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
+ fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+ &mut self.interactivity
+ }
+}
+
+impl<V: 'static> Component<V> for UniformList<V> {
+ fn render(self) -> AnyElement<V> {
+ AnyElement::new(self)
+ }
+}
@@ -68,7 +68,8 @@ impl<T> Future for Task<T> {
}
}
}
-
+type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
+type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
impl BackgroundExecutor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher }
@@ -81,10 +82,16 @@ impl BackgroundExecutor {
R: Send + 'static,
{
let dispatcher = self.dispatcher.clone();
- let (runnable, task) =
- async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
- runnable.schedule();
- Task::Spawned(task)
+ fn inner<R: Send + 'static>(
+ dispatcher: Arc<dyn PlatformDispatcher>,
+ future: AnyFuture<R>,
+ ) -> Task<R> {
+ let (runnable, task) =
+ async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
+ runnable.schedule();
+ Task::Spawned(task)
+ }
+ inner::<R>(dispatcher, Box::pin(future))
}
#[cfg(any(test, feature = "test-support"))]
@@ -243,11 +250,17 @@ impl ForegroundExecutor {
R: 'static,
{
let dispatcher = self.dispatcher.clone();
- let (runnable, task) = async_task::spawn_local(future, move |runnable| {
- dispatcher.dispatch_on_main_thread(runnable)
- });
- runnable.schedule();
- Task::Spawned(task)
+ fn inner<R: 'static>(
+ dispatcher: Arc<dyn PlatformDispatcher>,
+ future: AnyLocalFuture<R>,
+ ) -> Task<R> {
+ let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+ dispatcher.dispatch_on_main_thread(runnable)
+ });
+ runnable.schedule();
+ Task::Spawned(task)
+ }
+ inner::<R>(dispatcher, Box::pin(future))
}
}
@@ -267,6 +267,24 @@ impl From<Size<Pixels>> for Size<GlobalPixels> {
}
}
+impl From<Size<Pixels>> for Size<DefiniteLength> {
+ fn from(size: Size<Pixels>) -> Self {
+ Size {
+ width: size.width.into(),
+ height: size.height.into(),
+ }
+ }
+}
+
+impl From<Size<Pixels>> for Size<AbsoluteLength> {
+ fn from(size: Size<Pixels>) -> Self {
+ Size {
+ width: size.width.into(),
+ height: size.height.into(),
+ }
+ }
+}
+
impl Size<Length> {
pub fn full() -> Self {
Self {
@@ -558,6 +576,15 @@ impl Edges<DefiniteLength> {
left: px(0.).into(),
}
}
+
+ pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
+ Edges {
+ top: self.top.to_pixels(parent_size.height, rem_size),
+ right: self.right.to_pixels(parent_size.width, rem_size),
+ bottom: self.bottom.to_pixels(parent_size.height, rem_size),
+ left: self.left.to_pixels(parent_size.width, rem_size),
+ }
+ }
}
impl Edges<AbsoluteLength> {
@@ -689,16 +716,16 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
pub struct Pixels(pub(crate) f32);
impl std::ops::Div for Pixels {
- type Output = Self;
+ type Output = f32;
fn div(self, rhs: Self) -> Self::Output {
- Self(self.0 / rhs.0)
+ self.0 / rhs.0
}
}
impl std::ops::DivAssign for Pixels {
fn div_assign(&mut self, rhs: Self) {
- self.0 /= rhs.0;
+ *self = Self(self.0 / rhs.0);
}
}
@@ -750,14 +777,6 @@ impl Pixels {
pub const ZERO: Pixels = Pixels(0.0);
pub const MAX: Pixels = Pixels(f32::MAX);
- pub fn as_usize(&self) -> usize {
- self.0 as usize
- }
-
- pub fn as_isize(&self) -> isize {
- self.0 as isize
- }
-
pub fn floor(&self) -> Self {
Self(self.0.floor())
}
@@ -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.
@@ -36,6 +37,7 @@ pub use anyhow::Result;
pub use app::*;
pub use assets::*;
pub use color::*;
+pub use ctor::ctor;
pub use element::*;
pub use elements::*;
pub use executor::*;
@@ -64,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::{
@@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
pub trait StatelessInteractive<V: 'static>: Element<V> {
- fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
+ fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
- self.stateless_interaction().hover_style = f(StyleRefinement::default());
+ self.stateless_interactivity().hover_style = f(StyleRefinement::default());
self
}
@@ -43,7 +43,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction().group_hover_style = Some(GroupStyle {
+ self.stateless_interactivity().group_hover_style = Some(GroupStyle {
group: group_name.into(),
style: f(StyleRefinement::default()),
});
@@ -58,7 +58,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble
@@ -79,7 +79,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.mouse_up_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble
@@ -100,7 +100,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture
@@ -121,7 +121,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.mouse_up_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture
@@ -141,7 +141,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.mouse_move_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@@ -158,7 +158,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.scroll_wheel_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@@ -174,23 +174,48 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
C: TryInto<DispatchContext>,
C::Error: Debug,
{
- self.stateless_interaction().dispatch_context =
+ self.stateless_interactivity().dispatch_context =
context.try_into().expect("invalid dispatch context");
self
}
+ /// Capture the given action, fires during the capture phase
+ fn capture_action<A: 'static>(
+ mut self,
+ listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.stateless_interactivity().key_listeners.push((
+ TypeId::of::<A>(),
+ Box::new(move |view, action, _dipatch_context, phase, cx| {
+ let action = action.downcast_ref().unwrap();
+ if phase == DispatchPhase::Capture {
+ listener(view, action, cx)
+ }
+ None
+ }),
+ ));
+ self
+ }
+
+ /// Add a listener for the given action, fires during the bubble event phase
fn on_action<A: 'static>(
mut self,
- listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static,
+ listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
- self.stateless_interaction().key_listeners.push((
+ self.stateless_interactivity().key_listeners.push((
TypeId::of::<A>(),
- Box::new(move |view, event, _, phase, cx| {
- let event = event.downcast_ref().unwrap();
- listener(view, event, phase, cx);
+ Box::new(move |view, action, _dispatch_context, phase, cx| {
+ let action = action.downcast_ref().unwrap();
+ if phase == DispatchPhase::Bubble {
+ listener(view, action, cx)
+ }
+
None
}),
));
@@ -204,7 +229,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction().key_listeners.push((
+ self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyDownEvent>(),
Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
@@ -222,7 +247,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction().key_listeners.push((
+ self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyUpEvent>(),
Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
@@ -237,7 +262,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction()
+ self.stateless_interactivity()
.drag_over_styles
.push((TypeId::of::<S>(), f(StyleRefinement::default())));
self
@@ -251,7 +276,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction().group_drag_over_styles.push((
+ self.stateless_interactivity().group_drag_over_styles.push((
TypeId::of::<S>(),
GroupStyle {
group: group_name.into(),
@@ -268,7 +293,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interaction().drop_listeners.push((
+ self.stateless_interactivity().drop_listeners.push((
TypeId::of::<W>(),
Box::new(move |view, dragged_view, cx| {
listener(view, dragged_view.downcast().unwrap(), cx);
@@ -279,13 +304,13 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
}
pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
- fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V>;
+ fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
- self.stateful_interaction().active_style = f(StyleRefinement::default());
+ self.stateful_interactivity().active_style = f(StyleRefinement::default());
self
}
@@ -297,7 +322,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
where
Self: Sized,
{
- self.stateful_interaction().group_active_style = Some(GroupStyle {
+ self.stateful_interactivity().group_active_style = Some(GroupStyle {
group: group_name.into(),
style: f(StyleRefinement::default()),
});
@@ -311,7 +336,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
where
Self: Sized,
{
- self.stateful_interaction()
+ self.stateful_interactivity()
.click_listeners
.push(Box::new(move |view, event, cx| listener(view, event, cx)));
self
@@ -326,10 +351,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
W: 'static + Render,
{
debug_assert!(
- self.stateful_interaction().drag_listener.is_none(),
+ self.stateful_interactivity().drag_listener.is_none(),
"calling on_drag more than once on the same element is not supported"
);
- self.stateful_interaction().drag_listener =
+ self.stateful_interactivity().drag_listener =
Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
view: listener(view_state, cx).into(),
cursor_offset,
@@ -342,10 +367,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
Self: Sized,
{
debug_assert!(
- self.stateful_interaction().hover_listener.is_none(),
+ self.stateful_interactivity().hover_listener.is_none(),
"calling on_hover more than once on the same element is not supported"
);
- self.stateful_interaction().hover_listener = Some(Box::new(listener));
+ self.stateful_interactivity().hover_listener = Some(Box::new(listener));
self
}
@@ -358,10 +383,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
W: 'static + Render,
{
debug_assert!(
- self.stateful_interaction().tooltip_builder.is_none(),
+ self.stateful_interactivity().tooltip_builder.is_none(),
"calling tooltip more than once on the same element is not supported"
);
- self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| {
+ self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
build_tooltip(view_state, cx).into()
}));
@@ -369,11 +394,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
}
}
-pub trait ElementInteraction<V: 'static>: 'static {
- fn as_stateless(&self) -> &StatelessInteraction<V>;
- fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
- fn as_stateful(&self) -> Option<&StatefulInteraction<V>>;
- fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>>;
+pub trait ElementInteractivity<V: 'static>: 'static {
+ fn as_stateless(&self) -> &StatelessInteractivity<V>;
+ fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
+ fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
+ fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
fn initialize<R>(
&mut self,
@@ -382,6 +407,8 @@ pub trait ElementInteraction<V: 'static>: 'static {
) -> R {
if let Some(stateful) = self.as_stateful_mut() {
cx.with_element_id(stateful.id.clone(), |global_id, cx| {
+ // In addition to any key down/up listeners registered directly on the element,
+ // we also add a key listener to match actions from the keymap.
stateful.key_listeners.push((
TypeId::of::<KeyDownEvent>(),
Box::new(move |_, key_down, context, phase, cx| {
@@ -736,11 +763,11 @@ pub trait ElementInteraction<V: 'static>: 'static {
}
#[derive(Deref, DerefMut)]
-pub struct StatefulInteraction<V> {
+pub struct StatefulInteractivity<V> {
pub id: ElementId,
#[deref]
#[deref_mut]
- stateless: StatelessInteraction<V>,
+ stateless: StatelessInteractivity<V>,
click_listeners: SmallVec<[ClickListener<V>; 2]>,
active_style: StyleRefinement,
group_active_style: Option<GroupStyle>,
@@ -749,42 +776,42 @@ pub struct StatefulInteraction<V> {
tooltip_builder: Option<TooltipBuilder<V>>,
}
-impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
- fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
+impl<V: 'static> StatefulInteractivity<V> {
+ pub fn new(id: ElementId, stateless: StatelessInteractivity<V>) -> Self {
+ Self {
+ id,
+ stateless,
+ click_listeners: SmallVec::new(),
+ active_style: StyleRefinement::default(),
+ group_active_style: None,
+ drag_listener: None,
+ hover_listener: None,
+ tooltip_builder: None,
+ }
+ }
+}
+
+impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
+ fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
Some(self)
}
- fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
+ fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
Some(self)
}
- fn as_stateless(&self) -> &StatelessInteraction<V> {
+ fn as_stateless(&self) -> &StatelessInteractivity<V> {
&self.stateless
}
- fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
+ fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
&mut self.stateless
}
}
-impl<V> From<ElementId> for StatefulInteraction<V> {
- fn from(id: ElementId) -> Self {
- Self {
- id,
- stateless: StatelessInteraction::default(),
- click_listeners: SmallVec::new(),
- drag_listener: None,
- hover_listener: None,
- tooltip_builder: None,
- active_style: StyleRefinement::default(),
- group_active_style: None,
- }
- }
-}
-
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
-pub struct StatelessInteraction<V> {
+pub struct StatelessInteractivity<V> {
pub dispatch_context: DispatchContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
@@ -798,9 +825,9 @@ pub struct StatelessInteraction<V> {
drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
}
-impl<V> StatelessInteraction<V> {
- pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteraction<V> {
- StatefulInteraction {
+impl<V> StatelessInteractivity<V> {
+ pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
+ StatefulInteractivity {
id: id.into(),
stateless: self,
click_listeners: SmallVec::new(),
@@ -876,9 +903,15 @@ impl InteractiveElementState {
.as_ref()
.map(|offset| offset.lock().clone())
}
+
+ pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
+ self.scroll_offset
+ .get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
+ .clone()
+ }
}
-impl<V> Default for StatelessInteraction<V> {
+impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self {
Self {
dispatch_context: DispatchContext::default(),
@@ -896,20 +929,20 @@ impl<V> Default for StatelessInteraction<V> {
}
}
-impl<V: 'static> ElementInteraction<V> for StatelessInteraction<V> {
- fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
+impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
+ fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
None
}
- fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
+ fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
None
}
- fn as_stateless(&self) -> &StatelessInteraction<V> {
+ fn as_stateless(&self) -> &StatelessInteractivity<V> {
self
}
- fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
+ fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
self
}
}
@@ -1236,7 +1269,7 @@ pub type KeyListener<V> = Box<
mod test {
use crate::{
self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
- StatefulInteraction, StatelessInteractive, TestAppContext, VisualContext,
+ StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
};
struct TestView {
@@ -1248,19 +1281,13 @@ mod test {
actions!(TestAction);
impl Render for TestView {
- type Element = Div<Self, StatefulInteraction<Self>>;
+ type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child(
div()
- .on_key_down(|this: &mut TestView, _, _, _| {
- dbg!("ola!");
- this.saw_key_down = true
- })
- .on_action(|this: &mut TestView, _: &TestAction, _, _| {
- dbg!("ola!");
- this.saw_action = true
- })
+ .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
+ .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
.track_focus(&self.focus_handle),
)
}
@@ -87,7 +87,7 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged,
);
- let paths_rasterization_pipeline_state = build_pipeline_state(
+ let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
&device,
&library,
"paths_rasterization",
@@ -823,7 +823,40 @@ fn build_pipeline_state(
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
- descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
+
+ device
+ .new_render_pipeline_state(&descriptor)
+ .expect("could not create render pipeline state")
+}
+
+fn build_path_rasterization_pipeline_state(
+ device: &metal::DeviceRef,
+ library: &metal::LibraryRef,
+ label: &str,
+ vertex_fn_name: &str,
+ fragment_fn_name: &str,
+ pixel_format: metal::MTLPixelFormat,
+) -> metal::RenderPipelineState {
+ let vertex_fn = library
+ .get_function(vertex_fn_name, None)
+ .expect("error locating vertex function");
+ let fragment_fn = library
+ .get_function(fragment_fn_name, None)
+ .expect("error locating fragment function");
+
+ let descriptor = metal::RenderPipelineDescriptor::new();
+ descriptor.set_label(label);
+ descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
+ descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
+ let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
+ color_attachment.set_pixel_format(pixel_format);
+ color_attachment.set_blending_enabled(true);
+ color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
+ color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
+ color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
+ color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+ color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
+ color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
device
.new_render_pipeline_state(&descriptor)
@@ -5,10 +5,11 @@ using namespace metal;
float4 hsla_to_rgba(Hsla hsla);
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
- Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *viewport_size);
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size);
+float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
+ Bounds_ScaledPixels clip_bounds);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii);
float gaussian(float x, float sigma);
@@ -21,6 +22,14 @@ struct QuadVertexOutput {
float4 background_color [[flat]];
float4 border_color [[flat]];
uint quad_id [[flat]];
+ float clip_distance [[clip_distance]][4];
+};
+
+struct QuadFragmentInput {
+ float4 position [[position]];
+ float4 background_color [[flat]];
+ float4 border_color [[flat]];
+ uint quad_id [[flat]];
};
vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
@@ -33,15 +42,21 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
[[buffer(QuadInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Quad quad = quads[quad_id];
- float4 device_position = to_device_position(
- unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
+ float4 device_position =
+ to_device_position(unit_vertex, quad.bounds, viewport_size);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
+ quad.content_mask.bounds);
float4 background_color = hsla_to_rgba(quad.background);
float4 border_color = hsla_to_rgba(quad.border_color);
- return QuadVertexOutput{device_position, background_color, border_color,
- quad_id};
+ return QuadVertexOutput{
+ device_position,
+ background_color,
+ border_color,
+ quad_id,
+ {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
}
-fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
+fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
constant Quad *quads
[[buffer(QuadInputIndex_Quads)]]) {
Quad quad = quads[input.quad_id];
@@ -117,6 +132,13 @@ struct ShadowVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint shadow_id [[flat]];
+ float clip_distance [[clip_distance]][4];
+};
+
+struct ShadowFragmentInput {
+ float4 position [[position]];
+ float4 color [[flat]];
+ uint shadow_id [[flat]];
};
vertex ShadowVertexOutput shadow_vertex(
@@ -137,18 +159,20 @@ vertex ShadowVertexOutput shadow_vertex(
bounds.size.width += 2. * margin;
bounds.size.height += 2. * margin;
- float4 device_position = to_device_position(
- unit_vertex, bounds, shadow.content_mask.bounds, viewport_size);
+ float4 device_position =
+ to_device_position(unit_vertex, bounds, viewport_size);
+ float4 clip_distance =
+ distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
float4 color = hsla_to_rgba(shadow.color);
return ShadowVertexOutput{
device_position,
color,
shadow_id,
- };
+ {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
}
-fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
+fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
constant Shadow *shadows
[[buffer(ShadowInputIndex_Shadows)]]) {
Shadow shadow = shadows[input.shadow_id];
@@ -197,6 +221,13 @@ struct UnderlineVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint underline_id [[flat]];
+ float clip_distance [[clip_distance]][4];
+};
+
+struct UnderlineFragmentInput {
+ float4 position [[position]];
+ float4 color [[flat]];
+ uint underline_id [[flat]];
};
vertex UnderlineVertexOutput underline_vertex(
@@ -208,13 +239,18 @@ vertex UnderlineVertexOutput underline_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id];
Underline underline = underlines[underline_id];
float4 device_position =
- to_device_position(unit_vertex, underline.bounds,
- underline.content_mask.bounds, viewport_size);
+ to_device_position(unit_vertex, underline.bounds, viewport_size);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
+ underline.content_mask.bounds);
float4 color = hsla_to_rgba(underline.color);
- return UnderlineVertexOutput{device_position, color, underline_id};
+ return UnderlineVertexOutput{
+ device_position,
+ color,
+ underline_id,
+ {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
}
-fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
+fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
constant Underline *underlines
[[buffer(UnderlineInputIndex_Underlines)]]) {
Underline underline = underlines[input.underline_id];
@@ -244,7 +280,13 @@ struct MonochromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
- uint sprite_id [[flat]];
+ float clip_distance [[clip_distance]][4];
+};
+
+struct MonochromeSpriteFragmentInput {
+ float4 position [[position]];
+ float2 tile_position;
+ float4 color [[flat]];
};
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
@@ -255,32 +297,31 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
[[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
-
float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id];
- // Don't apply content mask at the vertex level because we don't have time
- // to make sampling from the texture match the mask.
- float4 device_position = to_device_position(unit_vertex, sprite.bounds,
- sprite.bounds, viewport_size);
+ float4 device_position =
+ to_device_position(unit_vertex, sprite.bounds, viewport_size);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
+ sprite.content_mask.bounds);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color);
- return MonochromeSpriteVertexOutput{device_position, tile_position, color,
- sprite_id};
+ return MonochromeSpriteVertexOutput{
+ device_position,
+ tile_position,
+ color,
+ {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
}
fragment float4 monochrome_sprite_fragment(
- MonochromeSpriteVertexOutput input [[stage_in]],
+ MonochromeSpriteFragmentInput input [[stage_in]],
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
- MonochromeSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
- float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
- Corners_ScaledPixels{0., 0., 0., 0.});
float4 color = input.color;
- color.a *= sample.a * saturate(0.5 - clip_distance);
+ color.a *= sample.a;
return color;
}
@@ -288,6 +329,13 @@ struct PolychromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
uint sprite_id [[flat]];
+ float clip_distance [[clip_distance]][4];
+};
+
+struct PolychromeSpriteFragmentInput {
+ float4 position [[position]];
+ float2 tile_position;
+ uint sprite_id [[flat]];
};
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
@@ -301,17 +349,20 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id];
PolychromeSprite sprite = sprites[sprite_id];
- // Don't apply content mask at the vertex level because we don't have time
- // to make sampling from the texture match the mask.
- float4 device_position = to_device_position(unit_vertex, sprite.bounds,
- sprite.bounds, viewport_size);
+ float4 device_position =
+ to_device_position(unit_vertex, sprite.bounds, viewport_size);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
+ sprite.content_mask.bounds);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
- return PolychromeSpriteVertexOutput{device_position, tile_position,
- sprite_id};
+ return PolychromeSpriteVertexOutput{
+ device_position,
+ tile_position,
+ sprite_id,
+ {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
}
fragment float4 polychrome_sprite_fragment(
- PolychromeSpriteVertexOutput input [[stage_in]],
+ PolychromeSpriteFragmentInput input [[stage_in]],
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PolychromeSprite sprite = sprites[input.sprite_id];
@@ -319,11 +370,8 @@ fragment float4 polychrome_sprite_fragment(
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
- float quad_distance =
+ float distance =
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
- float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
- Corners_ScaledPixels{0., 0., 0., 0.});
- float distance = max(quad_distance, clip_distance);
float4 color = sample;
if (sprite.grayscale) {
@@ -385,7 +433,6 @@ struct PathSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
- uint sprite_id [[flat]];
};
vertex PathSpriteVertexOutput path_sprite_vertex(
@@ -401,19 +448,17 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
PathSprite sprite = sprites[sprite_id];
// Don't apply content mask because it was already accounted for when
// rasterizing the path.
- float4 device_position = to_device_position(unit_vertex, sprite.bounds,
- sprite.bounds, viewport_size);
+ float4 device_position =
+ to_device_position(unit_vertex, sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color);
- return PathSpriteVertexOutput{device_position, tile_position, color,
- sprite_id};
+ return PathSpriteVertexOutput{device_position, tile_position, color};
}
fragment float4 path_sprite_fragment(
PathSpriteVertexOutput input [[stage_in]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
- PathSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
@@ -473,16 +518,10 @@ float4 hsla_to_rgba(Hsla hsla) {
}
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
- Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *input_viewport_size) {
float2 position =
unit_vertex * float2(bounds.size.width, bounds.size.height) +
float2(bounds.origin.x, bounds.origin.y);
- position.x = max(clip_bounds.origin.x, position.x);
- position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
- position.y = max(clip_bounds.origin.y, position.y);
- position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
-
float2 viewport_size = float2((float)input_viewport_size->width,
(float)input_viewport_size->height);
float2 device_position =
@@ -551,3 +590,14 @@ float blur_along_x(float x, float y, float sigma, float corner,
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
return integral.y - integral.x;
}
+
+float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
+ Bounds_ScaledPixels clip_bounds) {
+ float2 position =
+ unit_vertex * float2(bounds.size.width, bounds.size.height) +
+ float2(bounds.origin.x, bounds.origin.y);
+ return float4(position.x - clip_bounds.origin.x,
+ clip_bounds.origin.x + clip_bounds.size.width - position.x,
+ position.y - clip_bounds.origin.y,
+ clip_bounds.origin.y + clip_bounds.size.height - position.y);
+}
@@ -27,8 +27,8 @@ pub(crate) struct SceneBuilder {
polychrome_sprites: Vec<PolychromeSprite>,
}
-impl SceneBuilder {
- pub fn new() -> SceneBuilder {
+impl Default for SceneBuilder {
+ fn default() -> Self {
SceneBuilder {
layers_by_order: BTreeMap::new(),
splitter: BspSplitter::new(),
@@ -40,7 +40,9 @@ impl SceneBuilder {
polychrome_sprites: Vec::new(),
}
}
+}
+impl SceneBuilder {
pub fn build(&mut self) -> Scene {
// Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
let mut layer_z_values = vec![0.; self.layers_by_order.len()];
@@ -281,7 +281,7 @@ impl Style {
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
- cx.stack(0, |cx| {
+ cx.with_z_index(0, |cx| {
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
@@ -291,7 +291,7 @@ impl Style {
let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
- cx.stack(1, |cx| {
+ cx.with_z_index(1, |cx| {
cx.paint_quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
@@ -1,14 +1,19 @@
use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
- SharedString, StyleRefinement, Visibility,
+ SharedString, Style, StyleRefinement, Visibility,
};
use crate::{BoxShadow, TextStyleRefinement};
+use refineable::Refineable;
use smallvec::{smallvec, SmallVec};
pub trait Styled {
fn style(&mut self) -> &mut StyleRefinement;
+ fn computed_style(&mut self) -> Style {
+ Style::default().refined(self.style().clone())
+ }
+
gpui2_macros::style_helpers!();
/// Sets the size of the element to the full width and height.
@@ -74,7 +74,6 @@ impl Line {
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
- let glyph_origin = glyph_origin + baseline_offset;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end {
@@ -125,14 +124,14 @@ impl Line {
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
- glyph_origin,
+ glyph_origin + baseline_offset,
run.font_id,
glyph.id,
self.layout.layout.font_size,
)?;
} else {
cx.paint_glyph(
- glyph_origin,
+ glyph_origin + baseline_offset,
run.font_id,
glyph.id,
self.layout.layout.font_size,
@@ -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;
@@ -131,7 +132,12 @@ impl FocusHandle {
if self.id == ancestor_id {
return true;
} else {
- ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied();
+ ancestor = cx
+ .window
+ .current_frame
+ .focus_parents_by_child
+ .get(&ancestor_id)
+ .copied();
}
}
false
@@ -174,34 +180,39 @@ pub struct Window {
pub(crate) layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
- prev_frame_element_states: HashMap<GlobalElementId, AnyBox>,
- element_states: HashMap<GlobalElementId, AnyBox>,
- prev_frame_key_matchers: HashMap<GlobalElementId, KeyMatcher>,
- key_matchers: HashMap<GlobalElementId, KeyMatcher>,
- z_index_stack: StackingOrder,
- content_mask_stack: Vec<ContentMask<Pixels>>,
- element_offset_stack: Vec<Point<Pixels>>,
- mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
- key_dispatch_stack: Vec<KeyDispatchStackFrame>,
- freeze_key_dispatch_stack: bool,
- focus_stack: Vec<FocusId>,
- focus_parents_by_child: HashMap<FocusId, FocusId>,
- pub(crate) focus_listeners: Vec<AnyFocusListener>,
+ pub(crate) previous_frame: Frame,
+ pub(crate) current_frame: Frame,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
default_prevented: bool,
mouse_position: Point<Pixels>,
requested_cursor_style: Option<CursorStyle>,
+ requested_input_handler: Option<Box<dyn PlatformInputHandler>>,
scale_factor: f32,
bounds: WindowBounds,
bounds_observers: SubscriberSet<(), AnyObserver>,
active: bool,
activation_observers: SubscriberSet<(), AnyObserver>,
- pub(crate) scene_builder: SceneBuilder,
pub(crate) dirty: bool,
pub(crate) last_blur: Option<Option<FocusId>>,
pub(crate) focus: Option<FocusId>,
}
+#[derive(Default)]
+pub(crate) struct Frame {
+ element_states: HashMap<GlobalElementId, AnyBox>,
+ key_matchers: HashMap<GlobalElementId, KeyMatcher>,
+ mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
+ pub(crate) focus_listeners: Vec<AnyFocusListener>,
+ key_dispatch_stack: Vec<KeyDispatchStackFrame>,
+ freeze_key_dispatch_stack: bool,
+ focus_parents_by_child: HashMap<FocusId, FocusId>,
+ pub(crate) scene_builder: SceneBuilder,
+ z_index_stack: StackingOrder,
+ content_mask_stack: Vec<ContentMask<Pixels>>,
+ element_offset_stack: Vec<Point<Pixels>>,
+ focus_stack: Vec<FocusId>,
+}
+
impl Window {
pub(crate) fn new(
handle: AnyWindowHandle,
@@ -253,7 +264,7 @@ impl Window {
handle
.update(&mut cx, |_, cx| cx.dispatch_event(event))
.log_err()
- .unwrap_or(true)
+ .unwrap_or(false)
})
});
@@ -268,29 +279,18 @@ impl Window {
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
element_id_stack: GlobalElementId::default(),
- prev_frame_element_states: HashMap::default(),
- element_states: HashMap::default(),
- prev_frame_key_matchers: HashMap::default(),
- key_matchers: HashMap::default(),
- z_index_stack: StackingOrder(SmallVec::new()),
- content_mask_stack: Vec::new(),
- element_offset_stack: Vec::new(),
- mouse_listeners: HashMap::default(),
- key_dispatch_stack: Vec::new(),
- freeze_key_dispatch_stack: false,
- focus_stack: Vec::new(),
- focus_parents_by_child: HashMap::default(),
- focus_listeners: Vec::new(),
+ previous_frame: Frame::default(),
+ current_frame: Frame::default(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
default_prevented: true,
mouse_position,
requested_cursor_style: None,
+ requested_input_handler: None,
scale_factor,
bounds,
bounds_observers: SubscriberSet::new(),
active: false,
activation_observers: SubscriberSet::new(),
- scene_builder: SceneBuilder::new(),
dirty: true,
last_blur: None,
focus: None,
@@ -560,6 +560,12 @@ impl<'a> WindowContext<'a> {
.request_measured_layout(style, rem_size, measure)
}
+ pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
+ self.window
+ .layout_engine
+ .compute_layout(layout_id, available_space)
+ }
+
/// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
/// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
/// in order to pass your element its `Bounds` automatically.
@@ -605,6 +611,10 @@ impl<'a> WindowContext<'a> {
.find(|display| display.id() == self.window.display_id)
}
+ pub fn show_character_palette(&self) {
+ self.window.platform_window.show_character_palette();
+ }
+
/// The scale factor of the display associated with the window. For example, it could
/// return 2.0 for a "retina" display, indicating that each logical pixel should actually
/// be rendered as two pixels on screen.
@@ -654,8 +664,9 @@ impl<'a> WindowContext<'a> {
&mut self,
handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
) {
- let order = self.window.z_index_stack.clone();
+ let order = self.window.current_frame.z_index_stack.clone();
self.window
+ .current_frame
.mouse_listeners
.entry(TypeId::of::<Event>())
.or_default()
@@ -679,9 +690,9 @@ impl<'a> WindowContext<'a> {
/// Called during painting to invoke the given closure in a new stacking context. The given
/// z-index is interpreted relative to the previous call to `stack`.
pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
- self.window.z_index_stack.push(z_index);
+ self.window.current_frame.z_index_stack.push(z_index);
let result = f(self);
- self.window.z_index_stack.pop();
+ self.window.current_frame.z_index_stack.pop();
result
}
@@ -699,8 +710,8 @@ impl<'a> WindowContext<'a> {
let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
shadow_bounds.dilate(shadow.spread_radius);
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
Shadow {
order: 0,
bounds: shadow_bounds.scale(scale_factor),
@@ -727,8 +738,8 @@ impl<'a> WindowContext<'a> {
let content_mask = self.content_mask();
let window = &mut *self.window;
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
Quad {
order: 0,
bounds: bounds.scale(scale_factor),
@@ -748,9 +759,10 @@ impl<'a> WindowContext<'a> {
path.content_mask = content_mask;
path.color = color.into();
let window = &mut *self.window;
- window
- .scene_builder
- .insert(&window.z_index_stack, path.scale(scale_factor));
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
+ path.scale(scale_factor),
+ );
}
/// Paint an underline into the scene for the current frame at the current z-index.
@@ -772,8 +784,8 @@ impl<'a> WindowContext<'a> {
};
let content_mask = self.content_mask();
let window = &mut *self.window;
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
Underline {
order: 0,
bounds: bounds.scale(scale_factor),
@@ -787,6 +799,7 @@ impl<'a> WindowContext<'a> {
}
/// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
+ /// The y component of the origin is the baseline of the glyph.
pub fn paint_glyph(
&mut self,
origin: Point<Pixels>,
@@ -825,8 +838,8 @@ impl<'a> WindowContext<'a> {
};
let content_mask = self.content_mask().scale(scale_factor);
let window = &mut *self.window;
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
MonochromeSprite {
order: 0,
bounds,
@@ -840,6 +853,7 @@ impl<'a> WindowContext<'a> {
}
/// Paint an emoji glyph into the scene for the current frame at the current z-index.
+ /// The y component of the origin is the baseline of the glyph.
pub fn paint_emoji(
&mut self,
origin: Point<Pixels>,
@@ -875,8 +889,8 @@ impl<'a> WindowContext<'a> {
let content_mask = self.content_mask().scale(scale_factor);
let window = &mut *self.window;
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
PolychromeSprite {
order: 0,
bounds,
@@ -917,8 +931,8 @@ impl<'a> WindowContext<'a> {
let content_mask = self.content_mask().scale(scale_factor);
let window = &mut *self.window;
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
MonochromeSprite {
order: 0,
bounds,
@@ -953,8 +967,8 @@ impl<'a> WindowContext<'a> {
let corner_radii = corner_radii.scale(scale_factor);
let window = &mut *self.window;
- window.scene_builder.insert(
- &window.z_index_stack,
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
PolychromeSprite {
order: 0,
bounds,
@@ -999,7 +1013,7 @@ impl<'a> WindowContext<'a> {
}
self.window.root_view = Some(root_view);
- let scene = self.window.scene_builder.build();
+ let scene = self.window.current_frame.scene_builder.build();
self.window.platform_window.draw(scene);
let cursor_style = self
@@ -1008,43 +1022,28 @@ 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;
}
+ /// Rotate the current frame and the previous frame, then clear the current frame.
+ /// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) {
self.text_system().start_frame();
let window = &mut *self.window;
-
- // Move the current frame element states to the previous frame.
- // The new empty element states map will be populated for any element states we
- // reference during the upcoming frame.
- mem::swap(
- &mut window.element_states,
- &mut window.prev_frame_element_states,
- );
- window.element_states.clear();
-
- // Make the current key matchers the previous, and then clear the current.
- // An empty key matcher map will be created for every identified element in the
- // upcoming frame.
- mem::swap(
- &mut window.key_matchers,
- &mut window.prev_frame_key_matchers,
- );
- window.key_matchers.clear();
-
- // Clear mouse event listeners, because elements add new element listeners
- // when the upcoming frame is painted.
- window.mouse_listeners.values_mut().for_each(Vec::clear);
-
- // Clear focus state, because we determine what is focused when the new elements
- // in the upcoming frame are initialized.
- window.focus_listeners.clear();
- window.key_dispatch_stack.clear();
- window.focus_parents_by_child.clear();
- window.freeze_key_dispatch_stack = false;
+ mem::swap(&mut window.previous_frame, &mut window.current_frame);
+ let frame = &mut window.current_frame;
+ frame.element_states.clear();
+ frame.key_matchers.clear();
+ frame.mouse_listeners.values_mut().for_each(Vec::clear);
+ frame.focus_listeners.clear();
+ frame.key_dispatch_stack.clear();
+ frame.focus_parents_by_child.clear();
+ frame.freeze_key_dispatch_stack = false;
}
/// Dispatch a mouse or keyboard event on the window.
@@ -1108,6 +1107,7 @@ impl<'a> WindowContext<'a> {
if let Some(any_mouse_event) = event.mouse_event() {
if let Some(mut handlers) = self
.window
+ .current_frame
.mouse_listeners
.remove(&any_mouse_event.type_id())
{
@@ -1142,17 +1142,19 @@ impl<'a> WindowContext<'a> {
// Just in case any handlers added new handlers, which is weird, but possible.
handlers.extend(
self.window
+ .current_frame
.mouse_listeners
.get_mut(&any_mouse_event.type_id())
.into_iter()
.flat_map(|handlers| handlers.drain(..)),
);
self.window
+ .current_frame
.mouse_listeners
.insert(any_mouse_event.type_id(), handlers);
}
} else if let Some(any_key_event) = event.keyboard_event() {
- let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack);
+ let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
let key_event_type = any_key_event.type_id();
let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
@@ -1212,10 +1214,10 @@ impl<'a> WindowContext<'a> {
}
drop(context_stack);
- self.window.key_dispatch_stack = key_dispatch_stack;
+ self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
}
- true
+ !self.app.propagate_event
}
/// Attempt to map a keystroke to an action based on the keymap.
@@ -1227,13 +1229,14 @@ impl<'a> WindowContext<'a> {
) -> KeyMatch {
let key_match = self
.window
+ .current_frame
.key_matchers
.get_mut(element_id)
.unwrap()
.match_keystroke(keystroke, context_stack);
if key_match.is_some() {
- for matcher in self.window.key_matchers.values_mut() {
+ for matcher in self.window.current_frame.key_matchers.values_mut() {
matcher.clear_pending();
}
}
@@ -1493,11 +1496,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
window.element_id_stack.push(id.into());
let global_id = window.element_id_stack.clone();
- if window.key_matchers.get(&global_id).is_none() {
- window.key_matchers.insert(
+ if window.current_frame.key_matchers.get(&global_id).is_none() {
+ window.current_frame.key_matchers.insert(
global_id.clone(),
window
- .prev_frame_key_matchers
+ .previous_frame
+ .key_matchers
.remove(&global_id)
.unwrap_or_else(|| KeyMatcher::new(keymap)),
);
@@ -1517,9 +1521,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
f: impl FnOnce(&mut Self) -> R,
) -> R {
let mask = mask.intersect(&self.content_mask());
- self.window_mut().content_mask_stack.push(mask);
+ self.window_mut()
+ .current_frame
+ .content_mask_stack
+ .push(mask);
let result = f(self);
- self.window_mut().content_mask_stack.pop();
+ self.window_mut().current_frame.content_mask_stack.pop();
result
}
@@ -1535,15 +1542,19 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
};
let offset = self.element_offset() + offset;
- self.window_mut().element_offset_stack.push(offset);
+ self.window_mut()
+ .current_frame
+ .element_offset_stack
+ .push(offset);
let result = f(self);
- self.window_mut().element_offset_stack.pop();
+ self.window_mut().current_frame.element_offset_stack.pop();
result
}
/// Obtain the current element offset.
fn element_offset(&self) -> Point<Pixels> {
self.window()
+ .current_frame
.element_offset_stack
.last()
.copied()
@@ -1565,9 +1576,15 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
self.with_element_id(id, |global_id, cx| {
if let Some(any) = cx
.window_mut()
+ .current_frame
.element_states
.remove(&global_id)
- .or_else(|| cx.window_mut().prev_frame_element_states.remove(&global_id))
+ .or_else(|| {
+ cx.window_mut()
+ .previous_frame
+ .element_states
+ .remove(&global_id)
+ })
{
// Using the extra inner option to avoid needing to reallocate a new box.
let mut state_box = any
@@ -1578,11 +1595,15 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
.expect("element state is already on the stack");
let (result, state) = f(Some(state), cx);
state_box.replace(state);
- cx.window_mut().element_states.insert(global_id, state_box);
+ cx.window_mut()
+ .current_frame
+ .element_states
+ .insert(global_id, state_box);
result
} else {
let (result, state) = f(None, cx);
cx.window_mut()
+ .current_frame
.element_states
.insert(global_id, Box::new(Some(state)));
result
@@ -1610,6 +1631,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
/// Obtain the current content mask.
fn content_mask(&self) -> ContentMask<Pixels> {
self.window()
+ .current_frame
.content_mask_stack
.last()
.cloned()
@@ -1693,10 +1715,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
&mut self.window_cx
}
- pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
- self.window.z_index_stack.push(order);
+ pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+ self.window.current_frame.z_index_stack.push(z_index);
let result = f(self);
- self.window.z_index_stack.pop();
+ self.window.current_frame.z_index_stack.pop();
result
}
@@ -1851,11 +1873,14 @@ impl<'a, V: 'static> ViewContext<'a, V> {
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) {
let handle = self.view().downgrade();
- self.window.focus_listeners.push(Box::new(move |event, cx| {
- handle
- .update(cx, |view, cx| listener(view, event, cx))
- .log_err();
- }));
+ self.window
+ .current_frame
+ .focus_listeners
+ .push(Box::new(move |event, cx| {
+ handle
+ .update(cx, |view, cx| listener(view, event, cx))
+ .log_err();
+ }));
}
pub fn with_key_listeners<R>(
@@ -1863,8 +1888,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
- let old_stack_len = self.window.key_dispatch_stack.len();
- if !self.window.freeze_key_dispatch_stack {
+ let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
+ if !self.window.current_frame.freeze_key_dispatch_stack {
for (event_type, listener) in key_listeners {
let handle = self.view().downgrade();
let listener = Box::new(
@@ -1880,19 +1905,22 @@ impl<'a, V: 'static> ViewContext<'a, V> {
.flatten()
},
);
- self.window
- .key_dispatch_stack
- .push(KeyDispatchStackFrame::Listener {
+ self.window.current_frame.key_dispatch_stack.push(
+ KeyDispatchStackFrame::Listener {
event_type,
listener,
- });
+ },
+ );
}
}
let result = f(self);
- if !self.window.freeze_key_dispatch_stack {
- self.window.key_dispatch_stack.truncate(old_stack_len);
+ if !self.window.current_frame.freeze_key_dispatch_stack {
+ self.window
+ .current_frame
+ .key_dispatch_stack
+ .truncate(old_stack_len);
}
result
@@ -1907,16 +1935,17 @@ impl<'a, V: 'static> ViewContext<'a, V> {
return f(self);
}
- if !self.window.freeze_key_dispatch_stack {
+ if !self.window.current_frame.freeze_key_dispatch_stack {
self.window
+ .current_frame
.key_dispatch_stack
.push(KeyDispatchStackFrame::Context(context));
}
let result = f(self);
- if !self.window.freeze_key_dispatch_stack {
- self.window.key_dispatch_stack.pop();
+ if !self.window.previous_frame.freeze_key_dispatch_stack {
+ self.window.previous_frame.key_dispatch_stack.pop();
}
result
@@ -1927,20 +1956,21 @@ impl<'a, V: 'static> ViewContext<'a, V> {
focus_handle: FocusHandle,
f: impl FnOnce(&mut Self) -> R,
) -> R {
- if let Some(parent_focus_id) = self.window.focus_stack.last().copied() {
+ if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() {
self.window
+ .current_frame
.focus_parents_by_child
.insert(focus_handle.id, parent_focus_id);
}
- self.window.focus_stack.push(focus_handle.id);
+ self.window.current_frame.focus_stack.push(focus_handle.id);
if Some(focus_handle.id) == self.window.focus {
- self.window.freeze_key_dispatch_stack = true;
+ self.window.current_frame.freeze_key_dispatch_stack = true;
}
let result = f(self);
- self.window.focus_stack.pop();
+ self.window.current_frame.focus_stack.pop();
result
}
@@ -1995,6 +2025,19 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}
}
+impl<V> 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<V> ViewContext<'_, V>
where
V: EventEmitter,
@@ -0,0 +1,89 @@
+use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView};
+use std::{ops::Range, rc::Weak};
+
+pub struct WindowInputHandler<V>
+where
+ V: InputHandler,
+{
+ pub cx: Weak<AppCell>,
+ pub window: AnyWindowHandle,
+ pub handler: WeakView<V>,
+}
+
+impl<V: InputHandler + 'static> PlatformInputHandler for WindowInputHandler<V> {
+ fn selected_text_range(&self) -> Option<std::ops::Range<usize>> {
+ self.update(|view, cx| view.selected_text_range(cx))
+ .flatten()
+ }
+
+ fn marked_text_range(&self) -> Option<std::ops::Range<usize>> {
+ self.update(|view, cx| view.marked_text_range(cx)).flatten()
+ }
+
+ fn text_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<String> {
+ self.update(|view, cx| view.text_for_range(range_utf16, cx))
+ .flatten()
+ }
+
+ fn replace_text_in_range(
+ &mut self,
+ replacement_range: Option<std::ops::Range<usize>>,
+ 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<std::ops::Range<usize>>,
+ new_text: &str,
+ new_selected_range: Option<std::ops::Range<usize>>,
+ ) {
+ 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<usize>) -> Option<crate::Bounds<f32>> {
+ self.update(|view, cx| view.bounds_for_range(range_utf16, cx))
+ .flatten()
+ }
+}
+
+impl<V: InputHandler + 'static> WindowInputHandler<V> {
+ fn update<T>(&self, f: impl FnOnce(&mut V, &mut ViewContext<V>) -> T) -> Option<T> {
+ 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<usize>, cx: &mut ViewContext<Self>) -> Option<String>;
+ fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
+ fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
+ fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
+ fn replace_text_in_range(
+ &mut self,
+ range: Option<Range<usize>>,
+ text: &str,
+ cx: &mut ViewContext<Self>,
+ );
+ fn replace_and_mark_text_in_range(
+ &mut self,
+ range: Option<Range<usize>>,
+ new_text: &str,
+ new_selected_range: Option<Range<usize>>,
+ cx: &mut ViewContext<Self>,
+ );
+ fn bounds_for_range(
+ &self,
+ range_utf16: std::ops::Range<usize>,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<crate::Bounds<f32>>;
+}
@@ -0,0 +1,55 @@
+// Input:
+//
+// #[action]
+// struct Foo {
+// bar: String,
+// }
+
+// Output:
+//
+// #[gpui::register_action]
+// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
+// struct Foo {
+// bar: String,
+// }
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput};
+
+pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(item as DeriveInput);
+ let name = &input.ident;
+ let attrs = input
+ .attrs
+ .into_iter()
+ .filter(|attr| !attr.path.is_ident("action"))
+ .collect::<Vec<_>>();
+
+ let attributes = quote! {
+ #[gpui::register_action]
+ #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
+ #(#attrs)*
+ };
+ let visibility = input.vis;
+
+ let output = match input.data {
+ syn::Data::Struct(ref struct_data) => {
+ let fields = &struct_data.fields;
+ quote! {
+ #attributes
+ #visibility struct #name #fields
+ }
+ }
+ syn::Data::Enum(ref enum_data) => {
+ let variants = &enum_data.variants;
+ quote! {
+ #attributes
+ #visibility enum #name { #variants }
+ }
+ }
+ _ => panic!("Expected a struct or an enum."),
+ };
+
+ TokenStream::from(output)
+}
@@ -1,14 +1,26 @@
-use proc_macro::TokenStream;
-
+mod action;
mod derive_component;
+mod register_action;
mod style_helpers;
mod test;
+use proc_macro::TokenStream;
+
#[proc_macro]
pub fn style_helpers(args: TokenStream) -> TokenStream {
style_helpers::style_helpers(args)
}
+#[proc_macro_attribute]
+pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
+ action::action(attr, item)
+}
+
+#[proc_macro_attribute]
+pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
+ register_action::register_action(attr, item)
+}
+
#[proc_macro_derive(Component, attributes(component))]
pub fn derive_component(input: TokenStream) -> TokenStream {
derive_component::derive_component(input)
@@ -0,0 +1,33 @@
+// Input:
+//
+// struct FooBar {}
+
+// Output:
+//
+// struct FooBar {}
+//
+// #[allow(non_snake_case)]
+// #[gpui2::ctor]
+// fn register_foobar_builder() {
+// gpui2::register_action_builder::<Foo>()
+// }
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, DeriveInput};
+
+pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(item as DeriveInput);
+ let type_name = &input.ident;
+ let ctor_fn_name = format_ident!("register_{}_builder", type_name.to_string().to_lowercase());
+
+ let expanded = quote! {
+ #input
+ #[allow(non_snake_case)]
+ #[gpui::ctor]
+ fn #ctor_fn_name() {
+ gpui::register_action::<#type_name>()
+ }
+ };
+
+ TokenStream::from(expanded)
+}
@@ -10,3 +10,4 @@ doctest = false
[dependencies]
gpui = { package = "gpui2", path = "../gpui2" }
+serde = { workspace = true }
@@ -1,25 +1,12 @@
-// todo!(use actions! macro)
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct Cancel;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct Confirm;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SecondaryConfirm;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectPrev;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectNext;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectFirst;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct SelectLast;
-
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct ShowContextMenu;
+use gpui::actions;
+
+actions!(
+ Cancel,
+ Confirm,
+ SecondaryConfirm,
+ SelectPrev,
+ SelectNext,
+ SelectFirst,
+ SelectLast,
+ ShowContextMenu
+);
@@ -0,0 +1,28 @@
+[package]
+name = "picker2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/picker2.rs"
+doctest = false
+
+[dependencies]
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+menu = { package = "menu2", path = "../menu2" }
+settings = { package = "settings2", path = "../settings2" }
+util = { path = "../util" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+
+parking_lot.workspace = true
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+serde_json.workspace = true
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+ctor.workspace = true
+env_logger.workspace = true
@@ -0,0 +1,163 @@
+use editor::Editor;
+use gpui::{
+ div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
+ StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
+ WindowContext,
+};
+use std::cmp;
+
+pub struct Picker<D: PickerDelegate> {
+ pub delegate: D,
+ scroll_handle: UniformListScrollHandle,
+ editor: View<Editor>,
+ pending_update_matches: Option<Task<Option<()>>>,
+}
+
+pub trait PickerDelegate: Sized + 'static {
+ type ListItem: Component<Picker<Self>>;
+
+ fn match_count(&self) -> usize;
+ fn selected_index(&self) -> usize;
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+
+ // fn placeholder_text(&self) -> Arc<str>;
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+
+ fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
+
+ fn render_match(
+ &self,
+ ix: usize,
+ selected: bool,
+ cx: &mut ViewContext<Picker<Self>>,
+ ) -> Self::ListItem;
+}
+
+impl<D: PickerDelegate> Picker<D> {
+ pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
+ let editor = cx.build_view(|cx| Editor::single_line(cx));
+ cx.subscribe(&editor, Self::on_input_editor_event).detach();
+ Self {
+ delegate,
+ scroll_handle: UniformListScrollHandle::new(),
+ pending_update_matches: None,
+ editor,
+ }
+ }
+
+ pub fn focus(&self, cx: &mut WindowContext) {
+ self.editor.update(cx, |editor, cx| editor.focus(cx));
+ }
+
+ fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ let index = self.delegate.selected_index();
+ let ix = cmp::min(index + 1, count - 1);
+ self.delegate.set_selected_index(ix, cx);
+ self.scroll_handle.scroll_to_item(ix);
+ }
+ }
+
+ fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ let index = self.delegate.selected_index();
+ let ix = index.saturating_sub(1);
+ self.delegate.set_selected_index(ix, cx);
+ self.scroll_handle.scroll_to_item(ix);
+ }
+ }
+
+ fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ self.delegate.set_selected_index(0, cx);
+ self.scroll_handle.scroll_to_item(0);
+ }
+ }
+
+ fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ self.delegate.set_selected_index(count - 1, cx);
+ self.scroll_handle.scroll_to_item(count - 1);
+ }
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+ self.delegate.dismissed(cx);
+ }
+
+ fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+ self.delegate.confirm(false, cx);
+ }
+
+ fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+ self.delegate.confirm(true, cx);
+ }
+
+ fn on_input_editor_event(
+ &mut self,
+ _: View<Editor>,
+ event: &editor::Event,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if let editor::Event::BufferEdited = event {
+ let query = self.editor.read(cx).text(cx);
+ self.update_matches(query, cx);
+ }
+ }
+
+ pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
+ let update = self.delegate.update_matches(query, cx);
+ self.matches_updated(cx);
+ self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
+ update.await;
+ this.update(&mut cx, |this, cx| {
+ this.matches_updated(cx);
+ })
+ .ok()
+ }));
+ }
+
+ fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
+ let index = self.delegate.selected_index();
+ self.scroll_handle.scroll_to_item(index);
+ self.pending_update_matches = None;
+ cx.notify();
+ }
+}
+
+impl<D: PickerDelegate> Render for Picker<D> {
+ type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ div()
+ .context("picker")
+ .id("picker-container")
+ .focusable()
+ .size_full()
+ .on_action(Self::select_next)
+ .on_action(Self::select_prev)
+ .on_action(Self::select_first)
+ .on_action(Self::select_last)
+ .on_action(Self::cancel)
+ .on_action(Self::confirm)
+ .on_action(Self::secondary_confirm)
+ .child(self.editor.clone())
+ .child(
+ uniform_list("candidates", self.delegate.match_count(), {
+ move |this: &mut Self, visible_range, cx| {
+ let selected_ix = this.delegate.selected_index();
+ visible_range
+ .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
+ .collect()
+ }
+ })
+ .track_scroll(self.scroll_handle.clone())
+ .size_full(),
+ )
+ }
+}
@@ -73,9 +73,9 @@ impl KeymapFile {
"Expected first item in array to be a string."
)));
};
- cx.build_action(&name, Some(data))
+ gpui::build_action(&name, Some(data))
}
- Value::String(name) => cx.build_action(&name, None),
+ Value::String(name) => gpui::build_action(&name, None),
Value::Null => Ok(no_action()),
_ => {
return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
@@ -13,9 +13,12 @@ anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0"
clap = { version = "4.4", features = ["derive", "string"] }
+editor = { package = "editor2", path = "../editor2" }
chrono = "0.4"
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" }
itertools = "0.11.0"
+language = { package = "language2", path = "../language2" }
log.workspace = true
rust-embed.workspace = true
serde.workspace = true
@@ -25,8 +28,10 @@ smallvec.workspace = true
strum = { version = "0.25.0", features = ["derive"] }
theme = { path = "../theme" }
theme2 = { path = "../theme2" }
+menu = { package = "menu2", path = "../menu2" }
ui = { package = "ui2", path = "../ui2", features = ["stories"] }
util = { path = "../util" }
+picker = { package = "picker2", path = "../picker2" }
[dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
@@ -1,6 +1,7 @@
mod colors;
mod focus;
mod kitchen_sink;
+mod picker;
mod scroll;
mod text;
mod z_index;
@@ -8,6 +9,7 @@ mod z_index;
pub use colors::*;
pub use focus::*;
pub use kitchen_sink::*;
+pub use picker::*;
pub use scroll::*;
pub use text::*;
pub use z_index::*;
@@ -1,6 +1,6 @@
use gpui::{
actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
- StatefulInteraction, StatelessInteractive, Styled, View, VisualContext, WindowContext,
+ StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
};
use theme2::ActiveTheme;
@@ -15,24 +15,22 @@ impl FocusStory {
KeyBinding::new("cmd-a", ActionB, Some("child-1")),
KeyBinding::new("cmd-c", ActionC, None),
]);
- cx.register_action_type::<ActionA>();
- cx.register_action_type::<ActionB>();
cx.build_view(move |cx| Self {})
}
}
impl Render for FocusStory {
- type Element = Div<Self, StatefulInteraction<Self>, FocusEnabled<Self>>;
+ type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
- let color_1 = theme.styles.git.created;
- let color_2 = theme.styles.git.modified;
- let color_3 = theme.styles.git.deleted;
- let color_4 = theme.styles.git.conflict;
- let color_5 = theme.styles.git.ignored;
- let color_6 = theme.styles.git.renamed;
+ let color_1 = theme.status().created;
+ let color_2 = theme.status().modified;
+ let color_3 = theme.status().deleted;
+ let color_4 = theme.status().conflict;
+ let color_5 = theme.status().ignored;
+ let color_6 = theme.status().renamed;
let child_1 = cx.focus_handle();
let child_2 = cx.focus_handle();
@@ -40,20 +38,18 @@ impl Render for FocusStory {
.id("parent")
.focusable()
.context("parent")
- .on_action(|_, action: &ActionA, phase, cx| {
- println!("Action A dispatched on parent during {:?}", phase);
+ .on_action(|_, action: &ActionA, cx| {
+ println!("Action A dispatched on parent during");
})
- .on_action(|_, action: &ActionB, phase, cx| {
- println!("Action B dispatched on parent during {:?}", phase);
+ .on_action(|_, action: &ActionB, cx| {
+ println!("Action B dispatched on parent during");
})
.on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred"))
.on_focus_in(|_, _, _| println!("Parent focus_in"))
.on_focus_out(|_, _, _| println!("Parent focus_out"))
- .on_key_down(|_, event, phase, _| {
- println!("Key down on parent {:?} {:?}", phase, event)
- })
- .on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event))
+ .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event))
+ .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event))
.size_full()
.bg(color_1)
.focus(|style| style.bg(color_2))
@@ -62,8 +58,8 @@ impl Render for FocusStory {
div()
.track_focus(&child_1)
.context("child-1")
- .on_action(|_, action: &ActionB, phase, cx| {
- println!("Action B dispatched on child 1 during {:?}", phase);
+ .on_action(|_, action: &ActionB, cx| {
+ println!("Action B dispatched on child 1 during");
})
.w_full()
.h_6()
@@ -74,20 +70,16 @@ impl Render for FocusStory {
.on_blur(|_, _, _| println!("Child 1 blurred"))
.on_focus_in(|_, _, _| println!("Child 1 focus_in"))
.on_focus_out(|_, _, _| println!("Child 1 focus_out"))
- .on_key_down(|_, event, phase, _| {
- println!("Key down on child 1 {:?} {:?}", phase, event)
- })
- .on_key_up(|_, event, phase, _| {
- println!("Key up on child 1 {:?} {:?}", phase, event)
- })
+ .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event))
+ .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event))
.child("Child 1"),
)
.child(
div()
.track_focus(&child_2)
.context("child-2")
- .on_action(|_, action: &ActionC, phase, cx| {
- println!("Action C dispatched on child 2 during {:?}", phase);
+ .on_action(|_, action: &ActionC, cx| {
+ println!("Action C dispatched on child 2 during");
})
.w_full()
.h_6()
@@ -96,12 +88,8 @@ impl Render for FocusStory {
.on_blur(|_, _, _| println!("Child 2 blurred"))
.on_focus_in(|_, _, _| println!("Child 2 focus_in"))
.on_focus_out(|_, _, _| println!("Child 2 focus_out"))
- .on_key_down(|_, event, phase, _| {
- println!("Key down on child 2 {:?} {:?}", phase, event)
- })
- .on_key_up(|_, event, phase, _| {
- println!("Key up on child 2 {:?} {:?}", phase, event)
- })
+ .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event))
+ .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event))
.child("Child 2"),
)
}
@@ -1,5 +1,5 @@
use crate::{story::Story, story_selector::ComponentStory};
-use gpui::{Div, Render, StatefulInteraction, View, VisualContext};
+use gpui::{Div, Render, StatefulInteractivity, View, VisualContext};
use strum::IntoEnumIterator;
use ui::prelude::*;
@@ -12,7 +12,7 @@ impl KitchenSinkStory {
}
impl Render for KitchenSinkStory {
- type Element = Div<Self, StatefulInteraction<Self>>;
+ type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let component_stories = ComponentStory::iter()
@@ -0,0 +1,214 @@
+use std::sync::Arc;
+
+use fuzzy::StringMatchCandidate;
+use gpui::{
+ div, Component, Div, KeyBinding, ParentElement, Render, StatelessInteractive, Styled, Task,
+ View, VisualContext, WindowContext,
+};
+use picker::{Picker, PickerDelegate};
+use theme2::ActiveTheme;
+
+pub struct PickerStory {
+ picker: View<Picker<Delegate>>,
+}
+
+struct Delegate {
+ candidates: Arc<[StringMatchCandidate]>,
+ matches: Vec<usize>,
+ selected_ix: usize,
+}
+
+impl Delegate {
+ fn new(strings: &[&str]) -> Self {
+ Self {
+ candidates: strings
+ .iter()
+ .copied()
+ .enumerate()
+ .map(|(id, string)| StringMatchCandidate {
+ id,
+ char_bag: string.into(),
+ string: string.into(),
+ })
+ .collect(),
+ matches: vec![],
+ selected_ix: 0,
+ }
+ }
+}
+
+impl PickerDelegate for Delegate {
+ type ListItem = Div<Picker<Self>>;
+
+ fn match_count(&self) -> usize {
+ self.candidates.len()
+ }
+
+ fn render_match(
+ &self,
+ ix: usize,
+ selected: bool,
+ cx: &mut gpui::ViewContext<Picker<Self>>,
+ ) -> Self::ListItem {
+ let colors = cx.theme().colors();
+ let Some(candidate_ix) = self.matches.get(ix) else {
+ return div();
+ };
+ let candidate = self.candidates[*candidate_ix].string.clone();
+
+ div()
+ .text_color(colors.text)
+ .when(selected, |s| {
+ s.border_l_10().border_color(colors.terminal_ansi_yellow)
+ })
+ .hover(|style| {
+ style
+ .bg(colors.element_active)
+ .text_color(colors.text_accent)
+ })
+ .child(candidate)
+ }
+
+ fn selected_index(&self) -> usize {
+ self.selected_ix
+ }
+
+ fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
+ self.selected_ix = ix;
+ cx.notify();
+ }
+
+ fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
+ let candidate_ix = self.matches[self.selected_ix];
+ let candidate = self.candidates[candidate_ix].string.clone();
+
+ if secondary {
+ eprintln!("Secondary confirmed {}", candidate)
+ } else {
+ eprintln!("Confirmed {}", candidate)
+ }
+ }
+
+ fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
+ cx.quit();
+ }
+
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut gpui::ViewContext<Picker<Self>>,
+ ) -> Task<()> {
+ let candidates = self.candidates.clone();
+ self.matches = cx
+ .background_executor()
+ .block(fuzzy::match_strings(
+ &candidates,
+ &query,
+ true,
+ 100,
+ &Default::default(),
+ cx.background_executor().clone(),
+ ))
+ .into_iter()
+ .map(|r| r.candidate_id)
+ .collect();
+ self.selected_ix = 0;
+ Task::ready(())
+ }
+}
+
+impl PickerStory {
+ pub fn new(cx: &mut WindowContext) -> View<Self> {
+ cx.build_view(|cx| {
+ cx.bind_keys([
+ KeyBinding::new("up", menu::SelectPrev, Some("picker")),
+ KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
+ KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
+ KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
+ KeyBinding::new("down", menu::SelectNext, Some("picker")),
+ KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
+ KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
+ KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
+ KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
+ KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
+ KeyBinding::new("enter", menu::Confirm, Some("picker")),
+ KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")),
+ KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
+ KeyBinding::new("escape", menu::Cancel, Some("picker")),
+ KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
+ ]);
+
+ PickerStory {
+ picker: cx.build_view(|cx| {
+ let mut delegate = Delegate::new(&[
+ "Baguette (France)",
+ "Baklava (Turkey)",
+ "Beef Wellington (UK)",
+ "Biryani (India)",
+ "Borscht (Ukraine)",
+ "Bratwurst (Germany)",
+ "Bulgogi (Korea)",
+ "Burrito (USA)",
+ "Ceviche (Peru)",
+ "Chicken Tikka Masala (India)",
+ "Churrasco (Brazil)",
+ "Couscous (North Africa)",
+ "Croissant (France)",
+ "Dim Sum (China)",
+ "Empanada (Argentina)",
+ "Fajitas (Mexico)",
+ "Falafel (Middle East)",
+ "Feijoada (Brazil)",
+ "Fish and Chips (UK)",
+ "Fondue (Switzerland)",
+ "Goulash (Hungary)",
+ "Haggis (Scotland)",
+ "Kebab (Middle East)",
+ "Kimchi (Korea)",
+ "Lasagna (Italy)",
+ "Maple Syrup Pancakes (Canada)",
+ "Moussaka (Greece)",
+ "Pad Thai (Thailand)",
+ "Paella (Spain)",
+ "Pancakes (USA)",
+ "Pasta Carbonara (Italy)",
+ "Pavlova (Australia)",
+ "Peking Duck (China)",
+ "Pho (Vietnam)",
+ "Pierogi (Poland)",
+ "Pizza (Italy)",
+ "Poutine (Canada)",
+ "Pretzel (Germany)",
+ "Ramen (Japan)",
+ "Rendang (Indonesia)",
+ "Sashimi (Japan)",
+ "Satay (Indonesia)",
+ "Shepherd's Pie (Ireland)",
+ "Sushi (Japan)",
+ "Tacos (Mexico)",
+ "Tandoori Chicken (India)",
+ "Tortilla (Spain)",
+ "Tzatziki (Greece)",
+ "Wiener Schnitzel (Austria)",
+ ]);
+ delegate.update_matches("".into(), cx).detach();
+
+ let picker = Picker::new(delegate, cx);
+ picker.focus(cx);
+ picker
+ }),
+ }
+ })
+ }
+}
+
+impl Render for PickerStory {
+ type Element = Div<Self>;
+
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
+ div()
+ .bg(cx.theme().styles.colors.background)
+ .size_full()
+ .child(self.picker.clone())
+ }
+}
@@ -1,5 +1,5 @@
use gpui::{
- div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled,
+ div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled,
View, VisualContext, WindowContext,
};
use theme2::ActiveTheme;
@@ -13,12 +13,12 @@ impl ScrollStory {
}
impl Render for ScrollStory {
- type Element = Div<Self, StatefulInteraction<Self>>;
+ type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
- let color_1 = theme.styles.git.created;
- let color_2 = theme.styles.git.modified;
+ let color_1 = theme.status().created;
+ let color_2 = theme.status().modified;
div()
.id("parent")
@@ -38,6 +38,7 @@ pub enum ComponentStory {
Palette,
Panel,
ProjectPanel,
+ Players,
RecentProjects,
Scroll,
Tab,
@@ -51,6 +52,7 @@ pub enum ComponentStory {
TrafficLights,
Workspace,
ZIndex,
+ Picker,
}
impl ComponentStory {
@@ -79,6 +81,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(),
@@ -94,6 +97,7 @@ impl ComponentStory {
Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
Self::Workspace => ui::WorkspaceStory::view(cx).into(),
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
+ Self::Picker => PickerStory::new(cx).into(),
}
}
}
@@ -72,6 +72,8 @@ fn main() {
ThemeSettings::override_global(theme_settings, cx);
ui::settings::init(cx);
+ language::init(cx);
+ editor::init(cx);
let window = cx.open_window(
WindowOptions {
@@ -186,9 +186,9 @@ pub fn mouse_side(
}
pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
- let col = GridCol((pos.x / cur_size.cell_width).as_usize());
+ let col = GridCol((cur_size.cell_width / pos.x) as usize);
let col = min(col, cur_size.last_column());
- let line = (pos.y / cur_size.line_height).as_isize() as i32;
+ let line = (cur_size.line_height / pos.y) as i32;
let line = min(line, cur_size.bottommost_line().0);
AlacPoint::new(GridLine(line - display_offset as i32), col)
}
@@ -1121,8 +1121,7 @@ impl Terminal {
None => return,
};
- let scroll_lines =
- (scroll_delta / self.last_content.size.line_height).as_isize() as i32;
+ let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32;
self.events
.push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
@@ -1280,11 +1279,11 @@ impl Terminal {
}
/* Calculate the appropriate scroll lines */
TouchPhase::Moved => {
- let old_offset = (self.scroll_px / line_height).as_isize() as i32;
+ let old_offset = (self.scroll_px / line_height) as i32;
self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
- let new_offset = (self.scroll_px / line_height).as_isize() as i32;
+ let new_offset = (self.scroll_px / line_height) as i32;
// Whenever we hit the edges, reset our stored scroll to 0
// so we can respond to changes in direction quickly
@@ -1396,9 +1395,9 @@ fn all_search_matches<'a, T>(
}
fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
- let col = (pos.x / size.cell_width()).round().as_usize();
+ let col = (pos.x / size.cell_width()).round() as usize;
let clamped_col = min(col, size.columns() - 1);
- let row = (pos.y / size.line_height()).round().as_usize();
+ let row = (pos.y / size.line_height()).round() as usize;
let clamped_row = min(row, size.screen_lines() - 1);
clamped_row * size.columns() + clamped_col
}
@@ -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"] }
@@ -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<PlayerColor>);
-
-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 {
@@ -56,17 +29,6 @@ pub struct StatusColors {
pub warning: Hsla,
}
-#[derive(Refineable, Clone, Debug)]
-#[refineable(debug)]
-pub struct GitStatusColors {
- pub conflict: Hsla,
- pub created: Hsla,
- pub deleted: Hsla,
- pub ignored: Hsla,
- pub modified: Hsla,
- pub renamed: Hsla,
-}
-
#[derive(Refineable, Clone, Debug)]
#[refineable(debug, deserialize)]
pub struct ThemeColors {
@@ -287,7 +249,6 @@ pub struct ThemeStyles {
#[refineable]
pub colors: ThemeColors,
pub status: StatusColors,
- pub git: GitStatusColors,
pub player: PlayerColors,
pub syntax: Arc<SyntaxTheme>,
}
@@ -2,12 +2,104 @@ use std::num::ParseIntError;
use gpui::{hsla, Hsla, Rgba};
-use crate::{
- colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors},
- scale::{ColorScaleSet, ColorScales},
- syntax::SyntaxTheme,
- ColorScale,
-};
+use crate::colors::{StatusColors, SystemColors, ThemeColors};
+use crate::scale::{ColorScaleSet, ColorScales};
+use crate::syntax::SyntaxTheme;
+use crate::{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,61 +119,21 @@ 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(),
}
}
}
-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(),
- }
- }
-}
-
-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 {
@@ -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::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
+ default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
};
fn zed_pro_daylight() -> Theme {
@@ -14,8 +14,7 @@ fn zed_pro_daylight() -> Theme {
system: SystemColors::default(),
colors: ThemeColors::default_light(),
status: StatusColors::default(),
- git: GitStatusColors::default(),
- player: PlayerColors::default(),
+ player: PlayerColors::default_light(),
syntax: Arc::new(SyntaxTheme::default_light()),
},
}
@@ -30,7 +29,6 @@ pub(crate) fn zed_pro_moonlight() -> Theme {
system: SystemColors::default(),
colors: ThemeColors::default_dark(),
status: StatusColors::default(),
- git: GitStatusColors::default(),
player: PlayerColors::default(),
syntax: Arc::new(SyntaxTheme::default_dark()),
},
@@ -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<PlayerColor>);
+
+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<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> 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())
+ }),
+ )),
+ )
+ }
+ }
+}
@@ -6,8 +6,8 @@ use gpui::SharedString;
use refineable::Refineable;
use crate::{
- zed_pro_family, Appearance, GitStatusColors, PlayerColors, StatusColors, SyntaxTheme,
- SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
+ zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
+ ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
};
pub struct ThemeRegistry {
@@ -50,7 +50,6 @@ impl ThemeRegistry {
system: SystemColors::default(),
colors: theme_colors,
status: StatusColors::default(),
- git: GitStatusColors::default(),
player: PlayerColors::default(),
syntax: match user_theme.appearance {
Appearance::Light => Arc::new(SyntaxTheme::default_light()),
@@ -0,0 +1,38 @@
+use gpui::{div, Component, Div, ParentElement, Styled, ViewContext};
+
+use crate::ActiveTheme;
+
+pub struct Story {}
+
+impl Story {
+ pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
+ div()
+ .size_full()
+ .flex()
+ .flex_col()
+ .pt_2()
+ .px_4()
+ .font("Zed Mono")
+ .bg(cx.theme().colors().background)
+ }
+
+ pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
+ div()
+ .text_xl()
+ .text_color(cx.theme().colors().text)
+ .child(title.to_owned())
+ }
+
+ pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
+ Self::title(cx, std::any::type_name::<T>())
+ }
+
+ pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
+ div()
+ .mt_4()
+ .mb_2()
+ .text_xs()
+ .text_color(cx.theme().colors().text)
+ .child(label.to_owned())
+ }
+}
@@ -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::*;
@@ -93,19 +95,13 @@ impl Theme {
&self.styles.status
}
- /// Returns the [`GitStatusColors`] for the theme.
- #[inline(always)]
- pub fn git(&self) -> &GitStatusColors {
- &self.styles.git
- }
-
/// Returns the color for the syntax node with the given name.
#[inline(always)]
pub fn syntax_color(&self, name: &str) -> Hsla {
self.syntax().color(name)
}
- /// Returns the [`StatusColors`] for the theme.
+ /// Returns the [`DiagnosticStyle`] for the theme.
#[inline(always)]
pub fn diagnostic_style(&self) -> DiagnosticStyle {
DiagnosticStyle {
@@ -126,3 +122,8 @@ pub struct DiagnosticStyle {
pub hint: Hsla,
pub ignored: Hsla,
}
+
+#[cfg(feature = "stories")]
+mod story;
+#[cfg(feature = "stories")]
+pub use story::*;
@@ -2,8 +2,8 @@ use std::fmt::{self, Debug};
use gpui::{Hsla, Rgba};
use theme::{
- Appearance, GitStatusColors, PlayerColor, PlayerColors, StatusColors, SyntaxTheme,
- SystemColors, ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
+ Appearance, PlayerColor, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
+ ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
};
struct RawSyntaxPrinter<'a>(&'a str);
@@ -270,21 +270,6 @@ impl<'a> Debug for StatusColorsPrinter<'a> {
}
}
-pub struct GitStatusColorsPrinter<'a>(&'a GitStatusColors);
-
-impl<'a> Debug for GitStatusColorsPrinter<'a> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("GitStatusColors")
- .field("conflict", &HslaPrinter(self.0.conflict))
- .field("created", &HslaPrinter(self.0.created))
- .field("deleted", &HslaPrinter(self.0.deleted))
- .field("ignored", &HslaPrinter(self.0.ignored))
- .field("modified", &HslaPrinter(self.0.modified))
- .field("renamed", &HslaPrinter(self.0.renamed))
- .finish()
- }
-}
-
pub struct PlayerColorsPrinter<'a>(&'a PlayerColors);
impl<'a> Debug for PlayerColorsPrinter<'a> {
@@ -128,7 +128,7 @@ impl<V: 'static> Checkbox<V> {
// click area for the checkbox.
.size_5()
// Because we've enlarged the click area, we need to create a
- // `group` to pass down interaction events to the checkbox.
+ // `group` to pass down interactivity events to the checkbox.
.group(group_id.clone())
.child(
div()
@@ -148,7 +148,7 @@ impl<V: 'static> Checkbox<V> {
.bg(bg_color)
.border()
.border_color(border_color)
- // We only want the interaction states to fire when we
+ // We only want the interactivity states to fire when we
// are in a checkbox that isn't disabled.
.when(!self.disabled, |this| {
// Here instead of `hover()` we use `group_hover()`
@@ -46,12 +46,12 @@ pub enum GitStatus {
impl GitStatus {
pub fn hsla(&self, cx: &WindowContext) -> Hsla {
match self {
- Self::None => cx.theme().styles.system.transparent,
- Self::Created => cx.theme().styles.git.created,
- Self::Modified => cx.theme().styles.git.modified,
- Self::Deleted => cx.theme().styles.git.deleted,
- Self::Conflict => cx.theme().styles.git.conflict,
- Self::Renamed => cx.theme().styles.git.renamed,
+ Self::None => cx.theme().system().transparent,
+ Self::Created => cx.theme().status().created,
+ Self::Modified => cx.theme().status().modified,
+ Self::Deleted => cx.theme().status().deleted,
+ Self::Conflict => cx.theme().status().conflict,
+ Self::Renamed => cx.theme().status().renamed,
}
}
}
@@ -1,13 +1,11 @@
-use std::{any::TypeId, sync::Arc};
-
+use crate::Workspace;
use gpui::{
- div, px, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render,
- StatelessInteractive, Styled, View, ViewContext,
+ div, px, AnyView, AppContext, Component, Div, ParentElement, Render, StatelessInteractive,
+ Styled, View, ViewContext,
};
+use std::{any::TypeId, sync::Arc};
use ui::v_stack;
-use crate::Workspace;
-
pub struct ModalRegistry {
registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
}
@@ -43,14 +41,7 @@ impl ModalRegistry {
let build_view = build_view.clone();
div.on_action(
- move |workspace: &mut Workspace,
- event: &A,
- phase: DispatchPhase,
- cx: &mut ViewContext<Workspace>| {
- if phase == DispatchPhase::Capture {
- return;
- }
-
+ move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
let Some(new_modal) = (build_view)(workspace, cx) else {
return;
};
@@ -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)
}
}
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
-version = "0.112.0"
+version = "0.113.0"
publish = false
[lib]
@@ -37,4 +37,3 @@
((symbol) @comment
(#match? @comment "^#[cC][iIsS]$"))
-
@@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter {
&self,
cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> {
- let action_names = cx.all_action_names().collect::<Vec<_>>();
+ let action_names = gpui::all_action_names();
let staff_mode = cx.is_staff();
let language_names = &self.languages.language_names();
let settings_schema = cx.global::<SettingsStore>().json_schema(
@@ -37,4 +37,3 @@
((symbol) @comment
(#match? @comment "^#[cC][iIsS]$"))
-
@@ -2,7 +2,7 @@
const { execFileSync } = require("child_process");
const { GITHUB_ACCESS_TOKEN } = process.env;
-const PR_REGEX = /#\d+/ // Ex: matches on #4241
+const PR_REGEX = /#\d+/; // Ex: matches on #4241
const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im;
main();
@@ -12,7 +12,7 @@ async function main() {
const [newTag, oldTag] = execFileSync(
"git",
["tag", "--sort", "-committerdate"],
- { encoding: "utf8" }
+ { encoding: "utf8" },
)
.split("\n")
.filter((t) => t.startsWith("v") && t.endsWith("-pre"));
@@ -22,13 +22,22 @@ async function main() {
let hasProtocolChanges = false;
try {
- execFileSync("git", ["diff", oldTag, newTag, "--exit-code", "--", "crates/rpc"]).status != 0;
+ execFileSync("git", [
+ "diff",
+ oldTag,
+ newTag,
+ "--exit-code",
+ "--",
+ "crates/rpc",
+ ]).status != 0;
} catch (error) {
hasProtocolChanges = true;
}
if (hasProtocolChanges) {
- console.warn("\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n");
+ console.warn(
+ "\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n",
+ );
} else {
console.log("No RPC protocol changes\n");
}
@@ -37,10 +46,14 @@ async function main() {
const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag);
// Get the PRs that were cherry-picked between main and the old tag.
- const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag));
+ const existingPullRequestNumbers = new Set(
+ getPullRequestNumbers("main", oldTag),
+ );
// Filter out those existing PRs from the set of new PRs.
- const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number));
+ const newPullRequestNumbers = pullRequestNumbers.filter(
+ (number) => !existingPullRequestNumbers.has(number),
+ );
// Fetch the pull requests from the GitHub API.
console.log("Merged Pull requests:");
@@ -56,6 +69,16 @@ async function main() {
// Print the pull request title and URL.
const pullRequest = await response.json();
+ const releaseNotesHeader = /^\s*(?:Release )?Notes\s*:(.+)/ims;
+
+ let releaseNotes = pullRequest.body || "";
+ const captures = releaseNotesHeader.exec(releaseNotes);
+ const notes = captures ? captures[1] : "MISSING";
+ const skippableNoteRegex = /^\s*-?\s*n\/?a\s*/ims;
+
+ if (skippableNoteRegex.exec(notes) != null) {
+ continue;
+ }
console.log("*", pullRequest.title);
console.log(" PR URL: ", webURL);
@@ -66,38 +89,30 @@ async function main() {
console.log(" Issue URL: ", fixedIssueURL);
}
- let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1];
-
- if (releaseNotes) {
- releaseNotes = releaseNotes.trim().split("\n")
- console.log(" Release Notes:");
+ releaseNotes = notes.trim().split("\n");
+ console.log(" Release Notes:");
- for (const line of releaseNotes) {
- console.log(` ${line}`);
- }
+ for (const line of releaseNotes) {
+ console.log(` ${line}`);
}
- console.log()
+ console.log();
}
}
function getPullRequestNumbers(oldTag, newTag) {
const pullRequestNumbers = execFileSync(
"git",
- [
- "log",
- `${oldTag}..${newTag}`,
- "--oneline"
- ],
- { encoding: "utf8" }
+ ["log", `${oldTag}..${newTag}`, "--oneline"],
+ { encoding: "utf8" },
)
.split("\n")
- .filter(line => line.length > 0)
- .map(line => {
+ .filter((line) => line.length > 0)
+ .map((line) => {
const match = line.match(/#(\d+)/);
return match ? match[1] : null;
})
- .filter(line => line);
+ .filter((line) => line);
return pullRequestNumbers;
}