Merge branch 'main' into collab-renames
16
Cargo.lock
generated
@ -168,6 +168,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109"
|
||||
|
||||
[[package]]
|
||||
name = "assets"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
"rust-embed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-attributes"
|
||||
version = "1.1.2"
|
||||
@ -1112,6 +1121,7 @@ dependencies = [
|
||||
"sha-1 0.9.6",
|
||||
"sqlx 0.5.5",
|
||||
"surf",
|
||||
"theme",
|
||||
"tide",
|
||||
"tide-compress",
|
||||
"time 0.2.27",
|
||||
@ -4353,6 +4363,7 @@ dependencies = [
|
||||
"log",
|
||||
"postage",
|
||||
"project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"theme",
|
||||
@ -4517,6 +4528,8 @@ name = "settings"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assets",
|
||||
"collections",
|
||||
"gpui",
|
||||
"schemars",
|
||||
"serde",
|
||||
@ -5840,6 +5853,7 @@ checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
name = "vim"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assets",
|
||||
"collections",
|
||||
"editor",
|
||||
"gpui",
|
||||
@ -5847,6 +5861,7 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"project",
|
||||
"serde",
|
||||
"settings",
|
||||
"util",
|
||||
"workspace",
|
||||
@ -6114,6 +6129,7 @@ name = "zed"
|
||||
version = "0.25.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assets",
|
||||
"async-compression",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
|
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 499 B |
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 317 B |
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 516 B After Width: | Height: | Size: 516 B |
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 445 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 776 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 733 B After Width: | Height: | Size: 733 B |
Before Width: | Height: | Size: 676 B After Width: | Height: | Size: 676 B |
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
259
assets/keymaps/default.json
Normal file
@ -0,0 +1,259 @@
|
||||
{
|
||||
"*": {
|
||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd-alt-i": "workspace::DebugElements",
|
||||
"cmd-k cmd-left": "workspace::ActivatePreviousPane",
|
||||
"cmd-k cmd-right": "workspace::ActivateNextPane",
|
||||
"cmd-=": "zed::IncreaseBufferFontSize",
|
||||
"cmd--": "zed::DecreaseBufferFontSize",
|
||||
"cmd-,": "zed::OpenSettings"
|
||||
},
|
||||
"menu": {
|
||||
"up": "menu::SelectPrev",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm"
|
||||
},
|
||||
"Pane": {
|
||||
"shift-cmd-{": "pane::ActivatePrevItem",
|
||||
"shift-cmd-}": "pane::ActivateNextItem",
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
"alt-cmd-w": "pane::CloseInactiveItems",
|
||||
"ctrl--": "pane::GoBack",
|
||||
"shift-ctrl-_": "pane::GoForward",
|
||||
"cmd-k up": [
|
||||
"pane::Split",
|
||||
"Up"
|
||||
],
|
||||
"cmd-k down": [
|
||||
"pane::Split",
|
||||
"Down"
|
||||
],
|
||||
"cmd-k left": [
|
||||
"pane::Split",
|
||||
"Left"
|
||||
],
|
||||
"cmd-k right": [
|
||||
"pane::Split",
|
||||
"Right"
|
||||
],
|
||||
"cmd-shift-F": "project_search::ToggleFocus",
|
||||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-G": "search::SelectPrevMatch"
|
||||
},
|
||||
"Workspace": {
|
||||
"cmd-shift-F": "project_search::Deploy",
|
||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||
"cmd-k t": "theme_selector::Reload",
|
||||
"cmd-t": "project_symbols::Toggle",
|
||||
"cmd-p": "file_finder::Toggle",
|
||||
"alt-shift-D": "diagnostics::Deploy",
|
||||
"ctrl-alt-cmd-j": "journal::NewJournalEntry"
|
||||
},
|
||||
"ProjectSearchBar": {
|
||||
"enter": "project_search::Search",
|
||||
"cmd-enter": "project_search::SearchInNew"
|
||||
},
|
||||
"BufferSearchBar": {
|
||||
"escape": "buffer_search::Dismiss",
|
||||
"cmd-f": "buffer_search::FocusEditor",
|
||||
"enter": "search::SelectNextMatch",
|
||||
"shift-enter": "search::SelectPrevMatch"
|
||||
},
|
||||
"Editor": {
|
||||
"escape": "editor::Cancel",
|
||||
"backspace": "editor::Backspace",
|
||||
"ctrl-h": "editor::Backspace",
|
||||
"delete": "editor::Delete",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"cmd-[": "editor::Outdent",
|
||||
"cmd-]": "editor::Indent",
|
||||
"ctrl-shift-K": "editor::DeleteLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"alt-h": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
|
||||
"alt-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
|
||||
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"cmd-shift-D": "editor::DuplicateLine",
|
||||
"ctrl-cmd-up": "editor::MoveLineUp",
|
||||
"ctrl-cmd-down": "editor::MoveLineDown",
|
||||
"cmd-x": "editor::Cut",
|
||||
"cmd-c": "editor::Copy",
|
||||
"cmd-v": "editor::Paste",
|
||||
"cmd-z": "editor::Undo",
|
||||
"cmd-shift-Z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"down": "editor::MoveDown",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-b": "editor::MoveToPreviousWordStart",
|
||||
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
"alt-f": "editor::MoveToNextWordEnd",
|
||||
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
|
||||
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
|
||||
"cmd-left": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"cmd-right": "editor::MoveToEndOfLine",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"cmd-up": "editor::MoveToBeginning",
|
||||
"cmd-down": "editor::MoveToEnd",
|
||||
"shift-up": "editor::SelectUp",
|
||||
"ctrl-shift-P": "editor::SelectUp",
|
||||
"shift-down": "editor::SelectDown",
|
||||
"ctrl-shift-N": "editor::SelectDown",
|
||||
"shift-left": "editor::SelectLeft",
|
||||
"ctrl-shift-B": "editor::SelectLeft",
|
||||
"shift-right": "editor::SelectRight",
|
||||
"ctrl-shift-F": "editor::SelectRight",
|
||||
"alt-shift-left": "editor::SelectToPreviousWordStart",
|
||||
"alt-shift-B": "editor::SelectToPreviousWordStart",
|
||||
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
||||
"ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart",
|
||||
"alt-shift-right": "editor::SelectToNextWordEnd",
|
||||
"alt-shift-F": "editor::SelectToNextWordEnd",
|
||||
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||
"cmd-shift-up": "editor::SelectToBeginning",
|
||||
"cmd-shift-down": "editor::SelectToEnd",
|
||||
"cmd-a": "editor::SelectAll",
|
||||
"cmd-l": "editor::SelectLine",
|
||||
"cmd-shift-L": "editor::SplitSelectionIntoLines",
|
||||
"cmd-alt-up": "editor::AddSelectionAbove",
|
||||
"cmd-ctrl-p": "editor::AddSelectionAbove",
|
||||
"cmd-alt-down": "editor::AddSelectionBelow",
|
||||
"cmd-ctrl-n": "editor::AddSelectionBelow",
|
||||
"ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd",
|
||||
"cmd-shift-left": [
|
||||
"editor::SelectToBeginningOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"ctrl-shift-A": [
|
||||
"editor::SelectToBeginningOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"cmd-shift-right": [
|
||||
"editor::SelectToEndOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"ctrl-shift-E": [
|
||||
"editor::SelectToEndOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"cmd-d": [
|
||||
"editor::SelectNext",
|
||||
{
|
||||
"replace_newest": false
|
||||
}
|
||||
],
|
||||
"cmd-k cmd-d": [
|
||||
"editor::SelectNext",
|
||||
{
|
||||
"replace_newest": true
|
||||
}
|
||||
],
|
||||
"cmd-/": "editor::ToggleComments",
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-w": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"ctrl-shift-W": "editor::SelectSmallerSyntaxNode",
|
||||
"cmd-u": "editor::UndoSelection",
|
||||
"cmd-shift-U": "editor::RedoSelection",
|
||||
"f8": "editor::GoToNextDiagnostic",
|
||||
"shift-f8": "editor::GoToPrevDiagnostic",
|
||||
"f2": "editor::Rename",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"pageup": "editor::PageUp",
|
||||
"pagedown": "editor::PageDown",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
"alt-cmd-]": "editor::UnfoldLines",
|
||||
"alt-cmd-f": "editor::FoldSelectedRanges",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"cmd-.": "editor::ToggleCodeActions",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"cmd-f10": "editor::RestartLanguageServer"
|
||||
},
|
||||
"Editor && renaming": {
|
||||
"enter": "editor::ConfirmRename"
|
||||
},
|
||||
"Editor && showing_completions": {
|
||||
"enter": "editor::ConfirmCompletion"
|
||||
},
|
||||
"Editor && showing_code_actions": {
|
||||
"enter": "editor::ConfirmCodeAction"
|
||||
},
|
||||
"Editor && mode == full": {
|
||||
"enter": "editor::Newline",
|
||||
"cmd-f": [
|
||||
"buffer_search::Deploy",
|
||||
{
|
||||
"focus": true
|
||||
}
|
||||
],
|
||||
"cmd-e": [
|
||||
"buffer_search::Deploy",
|
||||
{
|
||||
"focus": false
|
||||
}
|
||||
],
|
||||
"cmd-shift-O": "outline::Toggle",
|
||||
"ctrl-g": "go_to_line::Toggle"
|
||||
},
|
||||
"Editor && mode == auto_height": {
|
||||
"alt-enter": [
|
||||
"editor::Input",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
"OutlineView": {
|
||||
"escape": "outline::Toggle"
|
||||
},
|
||||
"ProjectSymbolsView": {
|
||||
"escape": "project_symbols::Toggle"
|
||||
},
|
||||
"ThemeSelector": {
|
||||
"escape": "theme_selector::Toggle"
|
||||
},
|
||||
"GoToLine": {
|
||||
"escape": "go_to_line::Toggle",
|
||||
"enter": "go_to_line::Confirm"
|
||||
},
|
||||
"FileFinder": {
|
||||
"escape": "file_finder::Toggle"
|
||||
},
|
||||
"ChatPanel": {
|
||||
"enter": "chat_panel::Send"
|
||||
},
|
||||
"ProjectPanel": {
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry"
|
||||
}
|
||||
}
|
58
assets/keymaps/vim.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"Editor && vim_mode == insert": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore"
|
||||
},
|
||||
"Editor && vim_mode == normal && vim_submode == g": {
|
||||
"g": "vim::MoveToStart",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Normal": "None"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Editor && vim_mode == normal": {
|
||||
"i": [
|
||||
"vim::SwitchMode",
|
||||
"Insert"
|
||||
],
|
||||
"g": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Normal": "GPrefix"
|
||||
}
|
||||
],
|
||||
"h": "vim::MoveLeft",
|
||||
"j": "vim::MoveDown",
|
||||
"k": "vim::MoveUp",
|
||||
"l": "vim::MoveRight",
|
||||
"0": "vim::MoveToStartOfLine",
|
||||
"shift-$": "vim::MoveToEndOfLine",
|
||||
"shift-G": "vim::MoveToEnd",
|
||||
"w": [
|
||||
"vim::MoveToNextWordStart",
|
||||
false
|
||||
],
|
||||
"shift-W": [
|
||||
"vim::MoveToNextWordStart",
|
||||
true
|
||||
],
|
||||
"e": [
|
||||
"vim::MoveToNextWordEnd",
|
||||
false
|
||||
],
|
||||
"shift-E": [
|
||||
"vim::MoveToNextWordEnd",
|
||||
true
|
||||
],
|
||||
"b": [
|
||||
"vim::MoveToPreviousWordStart",
|
||||
false
|
||||
],
|
||||
"shift-B": [
|
||||
"vim::MoveToPreviousWordStart",
|
||||
true
|
||||
]
|
||||
}
|
||||
}
|
14
crates/assets/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "assets"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/assets.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
anyhow = "1.0.38"
|
||||
rust-embed = { version = "6.3", features = ["include-exclude"] }
|
||||
|
@ -3,7 +3,7 @@ use gpui::AssetSource;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "assets"]
|
||||
#[folder = "../../assets"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
|
@ -6,7 +6,6 @@ use editor::Editor;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
keymap::Binding,
|
||||
platform::CursorStyle,
|
||||
views::{ItemType, Select, SelectStyle},
|
||||
AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
|
||||
@ -38,8 +37,6 @@ actions!(chat_panel, [Send, LoadMoreMessages]);
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ChatPanel::send);
|
||||
cx.add_action(ChatPanel::load_more_messages);
|
||||
|
||||
cx.add_bindings(vec![Binding::new("enter", Send, Some("ChatPanel"))]);
|
||||
}
|
||||
|
||||
impl ChatPanel {
|
||||
|
@ -66,6 +66,7 @@ language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
|
@ -1117,6 +1117,7 @@ mod tests {
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use theme::ThemeRegistry;
|
||||
use util::TryFutureExt;
|
||||
use workspace::{Item, SplitDirection, ToggleFollow, Workspace, WorkspaceParams};
|
||||
|
||||
@ -2418,7 +2419,7 @@ mod tests {
|
||||
.condition(&cx_b, |editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.confirm_completion(&ConfirmCompletion(Some(0)), cx);
|
||||
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
|
||||
});
|
||||
|
||||
@ -3607,7 +3608,12 @@ mod tests {
|
||||
|
||||
// Toggle code actions and wait for them to display.
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.toggle_code_actions(&ToggleCodeActions(false), cx);
|
||||
editor.toggle_code_actions(
|
||||
&ToggleCodeActions {
|
||||
deployed_from_indicator: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
editor_b
|
||||
.condition(&cx_b, |editor, _| editor.context_menu_visible())
|
||||
@ -3618,7 +3624,7 @@ mod tests {
|
||||
// Confirming the code action will trigger a resolve request.
|
||||
let confirm_action = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
|
||||
Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
|
||||
})
|
||||
.unwrap();
|
||||
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
||||
@ -5633,6 +5639,7 @@ mod tests {
|
||||
project: project.clone(),
|
||||
user_store: self.user_store.clone(),
|
||||
languages: self.language_registry.clone(),
|
||||
themes: ThemeRegistry::new((), cx.font_cache().clone()),
|
||||
channel_list: cx.add_model(|cx| {
|
||||
ChannelList::new(self.user_store.clone(), self.client.clone(), cx)
|
||||
}),
|
||||
|
@ -8,9 +8,8 @@ use editor::{
|
||||
highlight_diagnostic_message, Editor, ExcerptId, MultiBuffer, ToOffset,
|
||||
};
|
||||
use gpui::{
|
||||
actions, elements::*, fonts::TextStyle, keymap::Binding, AnyViewHandle, AppContext, Entity,
|
||||
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
actions, elements::*, fonts::TextStyle, AnyViewHandle, AppContext, Entity, ModelHandle,
|
||||
MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
|
||||
@ -33,7 +32,6 @@ actions!(diagnostics, [Deploy]);
|
||||
const CONTEXT_LINE_COUNT: u32 = 1;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings([Binding::new("alt-shift-D", Deploy, Some("Workspace"))]);
|
||||
cx.add_action(ProjectDiagnosticsEditor::deploy);
|
||||
}
|
||||
|
||||
|
@ -22,8 +22,7 @@ use gpui::{
|
||||
executor,
|
||||
fonts::{self, HighlightStyle, TextStyle},
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
impl_actions,
|
||||
keymap::Binding,
|
||||
impl_actions, impl_internal_actions,
|
||||
platform::CursorStyle,
|
||||
text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
|
||||
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
@ -66,8 +65,11 @@ const MAX_LINE_LEN: usize = 1024;
|
||||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SelectNext(pub bool);
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct SelectNext {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GoToDiagnostic(pub Direction);
|
||||
@ -78,47 +80,38 @@ pub struct Scroll(pub Vector2F);
|
||||
#[derive(Clone)]
|
||||
pub struct Select(pub SelectPhase);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Input(pub String);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tab(pub Direction);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct SelectToBeginningOfLine {
|
||||
#[serde(default)]
|
||||
stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct SelectToEndOfLine {
|
||||
#[serde(default)]
|
||||
stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToggleCodeActions(pub bool);
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct ToggleCodeActions {
|
||||
#[serde(default)]
|
||||
pub deployed_from_indicator: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfirmCompletion(pub Option<usize>);
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
pub struct ConfirmCompletion {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfirmCodeAction(pub Option<usize>);
|
||||
|
||||
impl_actions!(
|
||||
editor,
|
||||
[
|
||||
SelectNext,
|
||||
GoToDiagnostic,
|
||||
Scroll,
|
||||
Select,
|
||||
Input,
|
||||
Tab,
|
||||
SelectToBeginningOfLine,
|
||||
SelectToEndOfLine,
|
||||
ToggleCodeActions,
|
||||
ConfirmCompletion,
|
||||
ConfirmCodeAction,
|
||||
]
|
||||
);
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
pub struct ConfirmCodeAction {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
actions!(
|
||||
editor,
|
||||
@ -127,6 +120,8 @@ actions!(
|
||||
Backspace,
|
||||
Delete,
|
||||
Newline,
|
||||
GoToNextDiagnostic,
|
||||
GoToPrevDiagnostic,
|
||||
Indent,
|
||||
Outdent,
|
||||
DeleteLine,
|
||||
@ -172,6 +167,8 @@ actions!(
|
||||
SplitSelectionIntoLines,
|
||||
AddSelectionAbove,
|
||||
AddSelectionBelow,
|
||||
Tab,
|
||||
TabPrev,
|
||||
ToggleComments,
|
||||
SelectLargerSyntaxNode,
|
||||
SelectSmallerSyntaxNode,
|
||||
@ -193,6 +190,21 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
impl_actions!(
|
||||
editor,
|
||||
[
|
||||
Input,
|
||||
SelectNext,
|
||||
SelectToBeginningOfLine,
|
||||
SelectToEndOfLine,
|
||||
ToggleCodeActions,
|
||||
ConfirmCompletion,
|
||||
ConfirmCodeAction,
|
||||
]
|
||||
);
|
||||
|
||||
impl_internal_actions!(editor, [Scroll, Select]);
|
||||
|
||||
enum DocumentHighlightRead {}
|
||||
enum DocumentHighlightWrite {}
|
||||
|
||||
@ -203,175 +215,6 @@ pub enum Direction {
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("escape", Cancel, Some("Editor")),
|
||||
Binding::new("backspace", Backspace, Some("Editor")),
|
||||
Binding::new("ctrl-h", Backspace, Some("Editor")),
|
||||
Binding::new("delete", Delete, Some("Editor")),
|
||||
Binding::new("ctrl-d", Delete, Some("Editor")),
|
||||
Binding::new("enter", Newline, Some("Editor && mode == full")),
|
||||
Binding::new(
|
||||
"alt-enter",
|
||||
Input("\n".into()),
|
||||
Some("Editor && mode == auto_height"),
|
||||
),
|
||||
Binding::new(
|
||||
"enter",
|
||||
ConfirmCompletion(None),
|
||||
Some("Editor && showing_completions"),
|
||||
),
|
||||
Binding::new(
|
||||
"enter",
|
||||
ConfirmCodeAction(None),
|
||||
Some("Editor && showing_code_actions"),
|
||||
),
|
||||
Binding::new("enter", ConfirmRename, Some("Editor && renaming")),
|
||||
Binding::new("tab", Tab(Direction::Next), Some("Editor")),
|
||||
Binding::new("shift-tab", Tab(Direction::Prev), Some("Editor")),
|
||||
Binding::new(
|
||||
"tab",
|
||||
ConfirmCompletion(None),
|
||||
Some("Editor && showing_completions"),
|
||||
),
|
||||
Binding::new("cmd-[", Outdent, Some("Editor")),
|
||||
Binding::new("cmd-]", Indent, Some("Editor")),
|
||||
Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
|
||||
Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
|
||||
Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
|
||||
Binding::new(
|
||||
"ctrl-alt-backspace",
|
||||
DeleteToPreviousSubwordStart,
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new("ctrl-alt-h", DeleteToPreviousSubwordStart, Some("Editor")),
|
||||
Binding::new("alt-delete", DeleteToNextWordEnd, Some("Editor")),
|
||||
Binding::new("alt-d", DeleteToNextWordEnd, Some("Editor")),
|
||||
Binding::new("ctrl-alt-delete", DeleteToNextSubwordEnd, Some("Editor")),
|
||||
Binding::new("ctrl-alt-d", DeleteToNextSubwordEnd, Some("Editor")),
|
||||
Binding::new("cmd-backspace", DeleteToBeginningOfLine, Some("Editor")),
|
||||
Binding::new("cmd-delete", DeleteToEndOfLine, Some("Editor")),
|
||||
Binding::new("ctrl-k", CutToEndOfLine, Some("Editor")),
|
||||
Binding::new("cmd-shift-D", DuplicateLine, Some("Editor")),
|
||||
Binding::new("ctrl-cmd-up", MoveLineUp, Some("Editor")),
|
||||
Binding::new("ctrl-cmd-down", MoveLineDown, Some("Editor")),
|
||||
Binding::new("cmd-x", Cut, Some("Editor")),
|
||||
Binding::new("cmd-c", Copy, Some("Editor")),
|
||||
Binding::new("cmd-v", Paste, Some("Editor")),
|
||||
Binding::new("cmd-z", Undo, Some("Editor")),
|
||||
Binding::new("cmd-shift-Z", Redo, Some("Editor")),
|
||||
Binding::new("up", MoveUp, Some("Editor")),
|
||||
Binding::new("down", MoveDown, Some("Editor")),
|
||||
Binding::new("left", MoveLeft, Some("Editor")),
|
||||
Binding::new("right", MoveRight, Some("Editor")),
|
||||
Binding::new("ctrl-p", MoveUp, Some("Editor")),
|
||||
Binding::new("ctrl-n", MoveDown, Some("Editor")),
|
||||
Binding::new("ctrl-b", MoveLeft, Some("Editor")),
|
||||
Binding::new("ctrl-f", MoveRight, Some("Editor")),
|
||||
Binding::new("alt-left", MoveToPreviousWordStart, Some("Editor")),
|
||||
Binding::new("alt-b", MoveToPreviousWordStart, Some("Editor")),
|
||||
Binding::new("ctrl-alt-left", MoveToPreviousSubwordStart, Some("Editor")),
|
||||
Binding::new("ctrl-alt-b", MoveToPreviousSubwordStart, Some("Editor")),
|
||||
Binding::new("alt-right", MoveToNextWordEnd, Some("Editor")),
|
||||
Binding::new("alt-f", MoveToNextWordEnd, Some("Editor")),
|
||||
Binding::new("ctrl-alt-right", MoveToNextSubwordEnd, Some("Editor")),
|
||||
Binding::new("ctrl-alt-f", MoveToNextSubwordEnd, Some("Editor")),
|
||||
Binding::new("cmd-left", MoveToBeginningOfLine, Some("Editor")),
|
||||
Binding::new("ctrl-a", MoveToBeginningOfLine, Some("Editor")),
|
||||
Binding::new("cmd-right", MoveToEndOfLine, Some("Editor")),
|
||||
Binding::new("ctrl-e", MoveToEndOfLine, Some("Editor")),
|
||||
Binding::new("cmd-up", MoveToBeginning, Some("Editor")),
|
||||
Binding::new("cmd-down", MoveToEnd, Some("Editor")),
|
||||
Binding::new("shift-up", SelectUp, Some("Editor")),
|
||||
Binding::new("ctrl-shift-P", SelectUp, Some("Editor")),
|
||||
Binding::new("shift-down", SelectDown, Some("Editor")),
|
||||
Binding::new("ctrl-shift-N", SelectDown, Some("Editor")),
|
||||
Binding::new("shift-left", SelectLeft, Some("Editor")),
|
||||
Binding::new("ctrl-shift-B", SelectLeft, Some("Editor")),
|
||||
Binding::new("shift-right", SelectRight, Some("Editor")),
|
||||
Binding::new("ctrl-shift-F", SelectRight, Some("Editor")),
|
||||
Binding::new("alt-shift-left", SelectToPreviousWordStart, Some("Editor")),
|
||||
Binding::new("alt-shift-B", SelectToPreviousWordStart, Some("Editor")),
|
||||
Binding::new(
|
||||
"ctrl-alt-shift-left",
|
||||
SelectToPreviousSubwordStart,
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new(
|
||||
"ctrl-alt-shift-B",
|
||||
SelectToPreviousSubwordStart,
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new("alt-shift-right", SelectToNextWordEnd, Some("Editor")),
|
||||
Binding::new("alt-shift-F", SelectToNextWordEnd, Some("Editor")),
|
||||
Binding::new(
|
||||
"cmd-shift-left",
|
||||
SelectToBeginningOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
},
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new(
|
||||
"ctrl-alt-shift-right",
|
||||
SelectToNextSubwordEnd,
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new("ctrl-alt-shift-F", SelectToNextSubwordEnd, Some("Editor")),
|
||||
Binding::new(
|
||||
"ctrl-shift-A",
|
||||
SelectToBeginningOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
},
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new(
|
||||
"cmd-shift-right",
|
||||
SelectToEndOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
},
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new(
|
||||
"ctrl-shift-E",
|
||||
SelectToEndOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
},
|
||||
Some("Editor"),
|
||||
),
|
||||
Binding::new("cmd-shift-up", SelectToBeginning, Some("Editor")),
|
||||
Binding::new("cmd-shift-down", SelectToEnd, Some("Editor")),
|
||||
Binding::new("cmd-a", SelectAll, Some("Editor")),
|
||||
Binding::new("cmd-l", SelectLine, Some("Editor")),
|
||||
Binding::new("cmd-shift-L", SplitSelectionIntoLines, Some("Editor")),
|
||||
Binding::new("cmd-alt-up", AddSelectionAbove, Some("Editor")),
|
||||
Binding::new("cmd-ctrl-p", AddSelectionAbove, Some("Editor")),
|
||||
Binding::new("cmd-alt-down", AddSelectionBelow, Some("Editor")),
|
||||
Binding::new("cmd-ctrl-n", AddSelectionBelow, Some("Editor")),
|
||||
Binding::new("cmd-d", SelectNext(false), Some("Editor")),
|
||||
Binding::new("cmd-k cmd-d", SelectNext(true), Some("Editor")),
|
||||
Binding::new("cmd-/", ToggleComments, Some("Editor")),
|
||||
Binding::new("alt-up", SelectLargerSyntaxNode, Some("Editor")),
|
||||
Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
|
||||
Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")),
|
||||
Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")),
|
||||
Binding::new("cmd-u", UndoSelection, Some("Editor")),
|
||||
Binding::new("cmd-shift-U", RedoSelection, Some("Editor")),
|
||||
Binding::new("f8", GoToDiagnostic(Direction::Next), Some("Editor")),
|
||||
Binding::new("shift-f8", GoToDiagnostic(Direction::Prev), Some("Editor")),
|
||||
Binding::new("f2", Rename, Some("Editor")),
|
||||
Binding::new("f12", GoToDefinition, Some("Editor")),
|
||||
Binding::new("alt-shift-f12", FindAllReferences, Some("Editor")),
|
||||
Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")),
|
||||
Binding::new("pageup", PageUp, Some("Editor")),
|
||||
Binding::new("pagedown", PageDown, Some("Editor")),
|
||||
Binding::new("alt-cmd-[", Fold, Some("Editor")),
|
||||
Binding::new("alt-cmd-]", UnfoldLines, Some("Editor")),
|
||||
Binding::new("alt-cmd-f", FoldSelectedRanges, Some("Editor")),
|
||||
Binding::new("ctrl-space", ShowCompletions, Some("Editor")),
|
||||
Binding::new("cmd-.", ToggleCodeActions(false), Some("Editor")),
|
||||
Binding::new("alt-enter", OpenExcerpts, Some("Editor")),
|
||||
Binding::new("cmd-f10", RestartLanguageServer, Some("Editor")),
|
||||
]);
|
||||
|
||||
cx.add_action(Editor::open_new);
|
||||
cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
|
||||
cx.add_action(Editor::select);
|
||||
@ -381,6 +224,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(Editor::backspace);
|
||||
cx.add_action(Editor::delete);
|
||||
cx.add_action(Editor::tab);
|
||||
cx.add_action(Editor::tab_prev);
|
||||
cx.add_action(Editor::indent);
|
||||
cx.add_action(Editor::outdent);
|
||||
cx.add_action(Editor::delete_line);
|
||||
@ -435,7 +279,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(Editor::move_to_enclosing_bracket);
|
||||
cx.add_action(Editor::undo_selection);
|
||||
cx.add_action(Editor::redo_selection);
|
||||
cx.add_action(Editor::go_to_diagnostic);
|
||||
cx.add_action(Editor::go_to_next_diagnostic);
|
||||
cx.add_action(Editor::go_to_prev_diagnostic);
|
||||
cx.add_action(Editor::go_to_definition);
|
||||
cx.add_action(Editor::page_up);
|
||||
cx.add_action(Editor::page_down);
|
||||
@ -833,7 +678,9 @@ impl CompletionsMenu {
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_mouse_down(move |cx| {
|
||||
cx.dispatch_action(ConfirmCompletion(Some(item_ix)));
|
||||
cx.dispatch_action(ConfirmCompletion {
|
||||
item_ix: Some(item_ix),
|
||||
});
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
@ -959,7 +806,9 @@ impl CodeActionsMenu {
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_mouse_down(move |cx| {
|
||||
cx.dispatch_action(ConfirmCodeAction(Some(item_ix)));
|
||||
cx.dispatch_action(ConfirmCodeAction {
|
||||
item_ix: Some(item_ix),
|
||||
});
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
@ -1851,33 +1700,37 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.mode != EditorMode::Full {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
if self.active_diagnostics.is_some() {
|
||||
self.dismiss_diagnostics(cx);
|
||||
} else if let Some(pending) = self.pending_selection.clone() {
|
||||
let mut selections = self.selections.clone();
|
||||
if selections.is_empty() {
|
||||
selections = Arc::from([pending.selection]);
|
||||
if self.mode == EditorMode::Full {
|
||||
if self.active_diagnostics.is_some() {
|
||||
self.dismiss_diagnostics(cx);
|
||||
return;
|
||||
}
|
||||
self.set_selections(selections, None, true, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
} else {
|
||||
let mut oldest_selection = self.oldest_selection::<usize>(&cx);
|
||||
if self.selection_count() == 1 {
|
||||
if oldest_selection.is_empty() {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(pending) = self.pending_selection.clone() {
|
||||
let mut selections = self.selections.clone();
|
||||
if selections.is_empty() {
|
||||
selections = Arc::from([pending.selection]);
|
||||
}
|
||||
self.set_selections(selections, None, true, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut oldest_selection = self.oldest_selection::<usize>(&cx);
|
||||
if self.selection_count() > 1 {
|
||||
self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if !oldest_selection.is_empty() {
|
||||
oldest_selection.start = oldest_selection.head().clone();
|
||||
oldest_selection.end = oldest_selection.head().clone();
|
||||
self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx);
|
||||
return;
|
||||
}
|
||||
self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@ -2457,7 +2310,7 @@ impl Editor {
|
||||
|
||||
pub fn confirm_completion(
|
||||
&mut self,
|
||||
ConfirmCompletion(completion_ix): &ConfirmCompletion,
|
||||
action: &ConfirmCompletion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
use language::ToOffset as _;
|
||||
@ -2470,7 +2323,7 @@ impl Editor {
|
||||
|
||||
let mat = completions_menu
|
||||
.matches
|
||||
.get(completion_ix.unwrap_or(completions_menu.selected_item))?;
|
||||
.get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
|
||||
let buffer_handle = completions_menu.buffer;
|
||||
let completion = completions_menu.completions.get(mat.candidate_id)?;
|
||||
|
||||
@ -2560,11 +2413,7 @@ impl Editor {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn toggle_code_actions(
|
||||
&mut self,
|
||||
&ToggleCodeActions(deployed_from_indicator): &ToggleCodeActions,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
|
||||
if matches!(
|
||||
self.context_menu.as_ref(),
|
||||
Some(ContextMenu::CodeActions(_))
|
||||
@ -2574,6 +2423,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let deployed_from_indicator = action.deployed_from_indicator;
|
||||
let mut task = self.code_actions_task.take();
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
while let Some(prev_task) = task {
|
||||
@ -2608,7 +2458,7 @@ impl Editor {
|
||||
|
||||
pub fn confirm_code_action(
|
||||
workspace: &mut Workspace,
|
||||
ConfirmCodeAction(action_ix): &ConfirmCodeAction,
|
||||
action: &ConfirmCodeAction,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
|
||||
@ -2619,7 +2469,7 @@ impl Editor {
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let action_ix = action_ix.unwrap_or(actions_menu.selected_item);
|
||||
let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
|
||||
let action = actions_menu.actions.get(action_ix)?.clone();
|
||||
let title = action.lsp_action.title.clone();
|
||||
let buffer = actions_menu.buffer;
|
||||
@ -2846,7 +2696,9 @@ impl Editor {
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_padding(Padding::uniform(3.))
|
||||
.on_mouse_down(|cx| {
|
||||
cx.dispatch_action(ToggleCodeActions(true));
|
||||
cx.dispatch_action(ToggleCodeActions {
|
||||
deployed_from_indicator: true,
|
||||
});
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
@ -2940,8 +2792,8 @@ impl Editor {
|
||||
self.move_to_snippet_tabstop(Bias::Right, cx)
|
||||
}
|
||||
|
||||
pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.move_to_snippet_tabstop(Bias::Left, cx);
|
||||
pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
self.move_to_snippet_tabstop(Bias::Left, cx)
|
||||
}
|
||||
|
||||
pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
|
||||
@ -3046,54 +2898,46 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn tab(&mut self, &Tab(direction): &Tab, cx: &mut ViewContext<Self>) {
|
||||
match direction {
|
||||
Direction::Prev => {
|
||||
if !self.snippet_stack.is_empty() {
|
||||
self.move_to_prev_snippet_tabstop(cx);
|
||||
return;
|
||||
}
|
||||
pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
|
||||
if self.move_to_prev_snippet_tabstop(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.outdent(&Outdent, cx);
|
||||
}
|
||||
Direction::Next => {
|
||||
if self.move_to_next_snippet_tabstop(cx) {
|
||||
return;
|
||||
}
|
||||
self.outdent(&Outdent, cx);
|
||||
}
|
||||
|
||||
let mut selections = self.local_selections::<Point>(cx);
|
||||
if selections.iter().all(|s| s.is_empty()) {
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
for selection in &mut selections {
|
||||
let language_name =
|
||||
buffer.language_at(selection.start, cx).map(|l| l.name());
|
||||
let tab_size =
|
||||
cx.global::<Settings>().tab_size(language_name.as_deref());
|
||||
let char_column = buffer
|
||||
.read(cx)
|
||||
.text_for_range(
|
||||
Point::new(selection.start.row, 0)..selection.start,
|
||||
)
|
||||
.flat_map(str::chars)
|
||||
.count();
|
||||
let chars_to_next_tab_stop =
|
||||
tab_size - (char_column as u32 % tab_size);
|
||||
buffer.edit(
|
||||
[selection.start..selection.start],
|
||||
" ".repeat(chars_to_next_tab_stop as usize),
|
||||
cx,
|
||||
);
|
||||
selection.start.column += chars_to_next_tab_stop;
|
||||
selection.end = selection.start;
|
||||
}
|
||||
});
|
||||
this.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
});
|
||||
} else {
|
||||
self.indent(&Indent, cx);
|
||||
}
|
||||
}
|
||||
pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||
if self.move_to_next_snippet_tabstop(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut selections = self.local_selections::<Point>(cx);
|
||||
if selections.iter().all(|s| s.is_empty()) {
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
for selection in &mut selections {
|
||||
let language_name =
|
||||
buffer.language_at(selection.start, cx).map(|l| l.name());
|
||||
let tab_size = cx.global::<Settings>().tab_size(language_name.as_deref());
|
||||
let char_column = buffer
|
||||
.read(cx)
|
||||
.text_for_range(Point::new(selection.start.row, 0)..selection.start)
|
||||
.flat_map(str::chars)
|
||||
.count();
|
||||
let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
|
||||
buffer.edit(
|
||||
[selection.start..selection.start],
|
||||
" ".repeat(chars_to_next_tab_stop as usize),
|
||||
cx,
|
||||
);
|
||||
selection.start.column += chars_to_next_tab_stop;
|
||||
selection.end = selection.start;
|
||||
}
|
||||
});
|
||||
this.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
});
|
||||
} else {
|
||||
self.indent(&Indent, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4237,7 +4081,6 @@ impl Editor {
|
||||
|
||||
pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
self.push_to_selection_history();
|
||||
let replace_newest = action.0;
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
let mut selections = self.local_selections::<usize>(cx);
|
||||
@ -4276,7 +4119,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
if let Some(next_selected_range) = next_selected_range {
|
||||
if replace_newest {
|
||||
if action.replace_newest {
|
||||
if let Some(newest_id) =
|
||||
selections.iter().max_by_key(|s| s.id).map(|s| s.id)
|
||||
{
|
||||
@ -4547,11 +4390,15 @@ impl Editor {
|
||||
self.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
}
|
||||
|
||||
pub fn go_to_diagnostic(
|
||||
&mut self,
|
||||
&GoToDiagnostic(direction): &GoToDiagnostic,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
fn go_to_next_diagnostic(&mut self, _: &GoToNextDiagnostic, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_diagnostic(Direction::Next, cx)
|
||||
}
|
||||
|
||||
fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_diagnostic(Direction::Prev, cx)
|
||||
}
|
||||
|
||||
pub fn go_to_diagnostic(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let selection = self.newest_selection_with_snapshot::<usize>(&buffer);
|
||||
let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
|
||||
@ -7771,7 +7618,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// indent from mid-tabstop to full tabstop
|
||||
view.tab(&Tab(Direction::Next), cx);
|
||||
view.tab(&Tab, cx);
|
||||
assert_text_with_selections(
|
||||
view,
|
||||
indoc! {"
|
||||
@ -7782,7 +7629,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// outdent from 1 tabstop to 0 tabstops
|
||||
view.tab(&Tab(Direction::Prev), cx);
|
||||
view.tab_prev(&TabPrev, cx);
|
||||
assert_text_with_selections(
|
||||
view,
|
||||
indoc! {"
|
||||
@ -7803,7 +7650,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// indent and outdent affect only the preceding line
|
||||
view.tab(&Tab(Direction::Next), cx);
|
||||
view.tab(&Tab, cx);
|
||||
assert_text_with_selections(
|
||||
view,
|
||||
indoc! {"
|
||||
@ -7812,7 +7659,7 @@ mod tests {
|
||||
] four"},
|
||||
cx,
|
||||
);
|
||||
view.tab(&Tab(Direction::Prev), cx);
|
||||
view.tab_prev(&TabPrev, cx);
|
||||
assert_text_with_selections(
|
||||
view,
|
||||
indoc! {"
|
||||
@ -7831,7 +7678,7 @@ mod tests {
|
||||
four"},
|
||||
cx,
|
||||
);
|
||||
view.tab(&Tab(Direction::Next), cx);
|
||||
view.tab(&Tab, cx);
|
||||
assert_text_with_selections(
|
||||
view,
|
||||
indoc! {"
|
||||
@ -7849,7 +7696,7 @@ mod tests {
|
||||
four"},
|
||||
cx,
|
||||
);
|
||||
view.tab(&Tab(Direction::Prev), cx);
|
||||
view.tab_prev(&TabPrev, cx);
|
||||
assert_text_with_selections(
|
||||
view,
|
||||
indoc! {"
|
||||
@ -7939,7 +7786,7 @@ mod tests {
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.tab(&Tab(Direction::Next), cx);
|
||||
editor.tab(&Tab, cx);
|
||||
assert_text_with_selections(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
@ -7950,7 +7797,7 @@ mod tests {
|
||||
"},
|
||||
cx,
|
||||
);
|
||||
editor.tab(&Tab(Direction::Prev), cx);
|
||||
editor.tab_prev(&TabPrev, cx);
|
||||
assert_text_with_selections(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
@ -8693,10 +8540,20 @@ mod tests {
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.select_ranges([ranges[1].start + 1..ranges[1].start + 1], None, cx);
|
||||
view.select_next(&SelectNext(false), cx);
|
||||
view.select_next(
|
||||
&SelectNext {
|
||||
replace_newest: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
assert_eq!(view.selected_ranges(cx), &ranges[1..2]);
|
||||
|
||||
view.select_next(&SelectNext(false), cx);
|
||||
view.select_next(
|
||||
&SelectNext {
|
||||
replace_newest: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
assert_eq!(view.selected_ranges(cx), &ranges[1..3]);
|
||||
|
||||
view.undo_selection(&UndoSelection, cx);
|
||||
@ -8705,10 +8562,20 @@ mod tests {
|
||||
view.redo_selection(&RedoSelection, cx);
|
||||
assert_eq!(view.selected_ranges(cx), &ranges[1..3]);
|
||||
|
||||
view.select_next(&SelectNext(false), cx);
|
||||
view.select_next(
|
||||
&SelectNext {
|
||||
replace_newest: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
assert_eq!(view.selected_ranges(cx), &ranges[1..4]);
|
||||
|
||||
view.select_next(&SelectNext(false), cx);
|
||||
view.select_next(
|
||||
&SelectNext {
|
||||
replace_newest: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
assert_eq!(view.selected_ranges(cx), &ranges[0..4]);
|
||||
});
|
||||
}
|
||||
@ -9363,7 +9230,7 @@ mod tests {
|
||||
let apply_additional_edits = editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&MoveDown, cx);
|
||||
let apply_additional_edits = editor
|
||||
.confirm_completion(&ConfirmCompletion(None), cx)
|
||||
.confirm_completion(&ConfirmCompletion::default(), cx)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
@ -9446,7 +9313,7 @@ mod tests {
|
||||
|
||||
let apply_additional_edits = editor.update(cx, |editor, cx| {
|
||||
let apply_additional_edits = editor
|
||||
.confirm_completion(&ConfirmCompletion(None), cx)
|
||||
.confirm_completion(&ConfirmCompletion::default(), cx)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
|
@ -1,12 +1,8 @@
|
||||
use editor::Editor;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
impl_actions,
|
||||
keymap::{self, Binding},
|
||||
AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
actions, elements::*, impl_internal_actions, keymap, AppContext, Axis, Entity, ModelHandle,
|
||||
MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::{Project, ProjectPath, WorktreeId};
|
||||
use settings::Settings;
|
||||
@ -41,8 +37,8 @@ pub struct FileFinder {
|
||||
#[derive(Clone)]
|
||||
pub struct Select(pub ProjectPath);
|
||||
|
||||
impl_actions!(file_finder, [Select]);
|
||||
actions!(file_finder, [Toggle]);
|
||||
impl_internal_actions!(file_finder, [Select]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(FileFinder::toggle);
|
||||
@ -50,11 +46,6 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(FileFinder::select);
|
||||
cx.add_action(FileFinder::select_prev);
|
||||
cx.add_action(FileFinder::select_next);
|
||||
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("cmd-p", Toggle, None),
|
||||
Binding::new("escape", Toggle, Some("FileFinder")),
|
||||
]);
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
|
||||
use gpui::{
|
||||
actions, elements::*, geometry::vector::Vector2F, keymap::Binding, Axis, Entity,
|
||||
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
||||
actions, elements::*, geometry::vector::Vector2F, Axis, Entity, MutableAppContext,
|
||||
RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use text::{Bias, Point};
|
||||
@ -10,11 +10,6 @@ use workspace::Workspace;
|
||||
actions!(go_to_line, [Toggle, Confirm]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings([
|
||||
Binding::new("ctrl-g", Toggle, Some("Editor")),
|
||||
Binding::new("escape", Toggle, Some("GoToLine")),
|
||||
Binding::new("enter", Confirm, Some("GoToLine")),
|
||||
]);
|
||||
cx.add_action(GoToLine::toggle);
|
||||
cx.add_action(GoToLine::confirm);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
|
||||
};
|
||||
pub use action::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::btree_map;
|
||||
use keymap::MatchResult;
|
||||
use lazy_static::lazy_static;
|
||||
@ -715,12 +715,14 @@ type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext
|
||||
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
||||
type GlobalObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
|
||||
|
||||
pub struct MutableAppContext {
|
||||
weak_self: Option<rc::Weak<RefCell<Self>>>,
|
||||
foreground_platform: Rc<dyn platform::ForegroundPlatform>,
|
||||
assets: Arc<AssetCache>,
|
||||
cx: AppContext,
|
||||
action_deserializers: HashMap<&'static str, DeserializeActionCallback>,
|
||||
capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
|
||||
actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
|
||||
global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
|
||||
@ -773,6 +775,7 @@ impl MutableAppContext {
|
||||
font_cache,
|
||||
platform,
|
||||
},
|
||||
action_deserializers: HashMap::new(),
|
||||
capture_actions: HashMap::new(),
|
||||
actions: HashMap::new(),
|
||||
global_actions: HashMap::new(),
|
||||
@ -857,6 +860,19 @@ impl MutableAppContext {
|
||||
.and_then(|(presenter, _)| presenter.borrow().debug_elements(self))
|
||||
}
|
||||
|
||||
pub fn deserialize_action(
|
||||
&self,
|
||||
name: &str,
|
||||
argument: Option<&str>,
|
||||
) -> Result<Box<dyn Action>> {
|
||||
let callback = self
|
||||
.action_deserializers
|
||||
.get(name)
|
||||
.ok_or_else(|| anyhow!("unknown action {}", name))?;
|
||||
callback(argument.unwrap_or("{}"))
|
||||
.with_context(|| format!("invalid data for action {}", name))
|
||||
}
|
||||
|
||||
pub fn add_action<A, V, F>(&mut self, handler: F)
|
||||
where
|
||||
A: Action,
|
||||
@ -899,6 +915,10 @@ impl MutableAppContext {
|
||||
},
|
||||
);
|
||||
|
||||
self.action_deserializers
|
||||
.entry(A::qualified_name())
|
||||
.or_insert(A::from_json_str);
|
||||
|
||||
let actions = if capture {
|
||||
&mut self.capture_actions
|
||||
} else {
|
||||
@ -934,6 +954,10 @@ impl MutableAppContext {
|
||||
handler(action, cx);
|
||||
});
|
||||
|
||||
self.action_deserializers
|
||||
.entry(A::qualified_name())
|
||||
.or_insert(A::from_json_str);
|
||||
|
||||
if self
|
||||
.global_actions
|
||||
.insert(TypeId::of::<A>(), handler)
|
||||
@ -1334,6 +1358,10 @@ impl MutableAppContext {
|
||||
self.keystroke_matcher.add_bindings(bindings);
|
||||
}
|
||||
|
||||
pub fn clear_bindings(&mut self) {
|
||||
self.keystroke_matcher.clear_bindings();
|
||||
}
|
||||
|
||||
pub fn dispatch_keystroke(
|
||||
&mut self,
|
||||
window_id: usize,
|
||||
@ -4575,7 +4603,8 @@ impl RefCounts {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{elements::*, impl_actions};
|
||||
use crate::{actions, elements::*, impl_actions};
|
||||
use serde::Deserialize;
|
||||
use smol::future::poll_once;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
@ -5683,6 +5712,42 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_deserialize_actions(cx: &mut MutableAppContext) {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct ComplexAction {
|
||||
arg: String,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
actions!(test::something, [SimpleAction]);
|
||||
impl_actions!(test::something, [ComplexAction]);
|
||||
|
||||
cx.add_global_action(move |_: &SimpleAction, _: &mut MutableAppContext| {});
|
||||
cx.add_global_action(move |_: &ComplexAction, _: &mut MutableAppContext| {});
|
||||
|
||||
let action1 = cx
|
||||
.deserialize_action(
|
||||
"test::something::ComplexAction",
|
||||
Some(r#"{"arg": "a", "count": 5}"#),
|
||||
)
|
||||
.unwrap();
|
||||
let action2 = cx
|
||||
.deserialize_action("test::something::SimpleAction", None)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
action1.as_any().downcast_ref::<ComplexAction>().unwrap(),
|
||||
&ComplexAction {
|
||||
arg: "a".to_string(),
|
||||
count: 5,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
action2.as_any().downcast_ref::<SimpleAction>().unwrap(),
|
||||
&SimpleAction
|
||||
);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_dispatch_action(cx: &mut MutableAppContext) {
|
||||
struct ViewA {
|
||||
@ -5721,32 +5786,32 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Action(pub &'static str);
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Action(pub String);
|
||||
|
||||
impl_actions!(test, [Action]);
|
||||
|
||||
let actions = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
{
|
||||
cx.add_global_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| {
|
||||
move |_: &Action, _: &mut MutableAppContext| {
|
||||
actions.borrow_mut().push("global".to_string());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
cx.add_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_action(move |view: &mut ViewA, action: &Action, cx| {
|
||||
move |view: &mut ViewA, action: &Action, cx| {
|
||||
assert_eq!(action.0, "bar");
|
||||
cx.propagate_action();
|
||||
actions.borrow_mut().push(format!("{} a", view.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
cx.add_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_action(move |view: &mut ViewA, _: &Action, cx| {
|
||||
move |view: &mut ViewA, _: &Action, cx| {
|
||||
if view.id != 1 {
|
||||
cx.add_view(|cx| {
|
||||
cx.propagate_action(); // Still works on a nested ViewContext
|
||||
@ -5754,32 +5819,32 @@ mod tests {
|
||||
});
|
||||
}
|
||||
actions.borrow_mut().push(format!("{} b", view.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
cx.add_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_action(move |view: &mut ViewB, _: &Action, cx| {
|
||||
move |view: &mut ViewB, _: &Action, cx| {
|
||||
cx.propagate_action();
|
||||
actions.borrow_mut().push(format!("{} c", view.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
cx.add_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_action(move |view: &mut ViewB, _: &Action, cx| {
|
||||
move |view: &mut ViewB, _: &Action, cx| {
|
||||
cx.propagate_action();
|
||||
actions.borrow_mut().push(format!("{} d", view.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
cx.capture_action({
|
||||
let actions = actions.clone();
|
||||
cx.capture_action(move |view: &mut ViewA, _: &Action, cx| {
|
||||
move |view: &mut ViewA, _: &Action, cx| {
|
||||
cx.propagate_action();
|
||||
actions.borrow_mut().push(format!("{} capture", view.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
|
||||
let view_2 = cx.add_view(window_id, |_| ViewB { id: 2 });
|
||||
@ -5789,7 +5854,7 @@ mod tests {
|
||||
cx.dispatch_action(
|
||||
window_id,
|
||||
vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()],
|
||||
&Action("bar"),
|
||||
&Action("bar".to_string()),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@ -5812,7 +5877,7 @@ mod tests {
|
||||
cx.dispatch_action(
|
||||
window_id,
|
||||
vec![view_2.id(), view_3.id(), view_4.id()],
|
||||
&Action("bar"),
|
||||
&Action("bar".to_string()),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@ -5832,8 +5897,8 @@ mod tests {
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_dispatch_keystroke(cx: &mut MutableAppContext) {
|
||||
#[derive(Clone)]
|
||||
pub struct Action(pub &'static str);
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Action(String);
|
||||
|
||||
impl_actions!(test, [Action]);
|
||||
|
||||
@ -5887,16 +5952,20 @@ mod tests {
|
||||
// "a" and "b" in its context, but not "c".
|
||||
cx.add_bindings(vec![keymap::Binding::new(
|
||||
"a",
|
||||
Action("a"),
|
||||
Action("a".to_string()),
|
||||
Some("a && b && !c"),
|
||||
)]);
|
||||
|
||||
cx.add_bindings(vec![keymap::Binding::new("b", Action("b"), None)]);
|
||||
cx.add_bindings(vec![keymap::Binding::new(
|
||||
"b",
|
||||
Action("b".to_string()),
|
||||
None,
|
||||
)]);
|
||||
|
||||
let actions = Rc::new(RefCell::new(Vec::new()));
|
||||
{
|
||||
cx.add_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_action(move |view: &mut View, action: &Action, cx| {
|
||||
move |view: &mut View, action: &Action, cx| {
|
||||
if action.0 == "a" {
|
||||
actions.borrow_mut().push(format!("{} a", view.id));
|
||||
} else {
|
||||
@ -5905,14 +5974,15 @@ mod tests {
|
||||
.push(format!("{} {}", view.id, action.0));
|
||||
cx.propagate_action();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
cx.add_global_action({
|
||||
let actions = actions.clone();
|
||||
cx.add_global_action(move |action: &Action, _| {
|
||||
move |action: &Action, _| {
|
||||
actions.borrow_mut().push(format!("global {}", action.0));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.dispatch_keystroke(
|
||||
window_id,
|
||||
|
@ -2,55 +2,108 @@ use std::any::{Any, TypeId};
|
||||
|
||||
pub trait Action: 'static {
|
||||
fn id(&self) -> TypeId;
|
||||
fn namespace(&self) -> &'static str;
|
||||
fn name(&self) -> &'static str;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
fn boxed_clone_as_any(&self) -> Box<dyn Any>;
|
||||
|
||||
fn qualified_name() -> &'static str
|
||||
where
|
||||
Self: Sized;
|
||||
fn from_json_str(json: &str) -> anyhow::Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Define a set of unit struct types that all implement the `Action` trait.
|
||||
///
|
||||
/// The first argument is a namespace that will be associated with each of
|
||||
/// the given action types, to ensure that they have globally unique
|
||||
/// qualified names for use in keymap files.
|
||||
#[macro_export]
|
||||
macro_rules! impl_actions {
|
||||
macro_rules! actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
impl $crate::action::Action for $name {
|
||||
fn id(&self) -> std::any::TypeId {
|
||||
std::any::TypeId::of::<$name>()
|
||||
}
|
||||
|
||||
fn namespace(&self) -> &'static str {
|
||||
stringify!($namespace)
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
stringify!($name)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn $crate::action::Action> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn boxed_clone_as_any(&self) -> Box<dyn std::any::Any> {
|
||||
Box::new(self.clone())
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct $name;
|
||||
$crate::__impl_action! {
|
||||
$namespace,
|
||||
$name,
|
||||
fn from_json_str(_: &str) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
|
||||
Ok(Box::new(Self))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement the `Action` trait for a set of existing types.
|
||||
///
|
||||
/// The first argument is a namespace that will be associated with each of
|
||||
/// the given action types, to ensure that they have globally unique
|
||||
/// qualified names for use in keymap files.
|
||||
#[macro_export]
|
||||
macro_rules! actions {
|
||||
macro_rules! impl_actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
|
||||
$(
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct $name;
|
||||
$crate::__impl_action! {
|
||||
$namespace,
|
||||
$name,
|
||||
fn from_json_str(json: &str) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
|
||||
Ok(Box::new($crate::serde_json::from_str::<Self>(json)?))
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
$crate::impl_actions!($namespace, [ $($name),* ]);
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement the `Action` trait for a set of existing types that are
|
||||
/// not intended to be constructed via a keymap file, but only dispatched
|
||||
/// internally.
|
||||
#[macro_export]
|
||||
macro_rules! impl_internal_actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
$crate::__impl_action! {
|
||||
$namespace,
|
||||
$name,
|
||||
fn from_json_str(_: &str) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
|
||||
Err($crate::anyhow::anyhow!("internal action"))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_action {
|
||||
($namespace:path, $name:ident, $from_json_fn:item) => {
|
||||
impl $crate::action::Action for $name {
|
||||
fn name(&self) -> &'static str {
|
||||
stringify!($name)
|
||||
}
|
||||
|
||||
fn qualified_name() -> &'static str {
|
||||
concat!(
|
||||
stringify!($namespace),
|
||||
"::",
|
||||
stringify!($name),
|
||||
)
|
||||
}
|
||||
|
||||
fn id(&self) -> std::any::TypeId {
|
||||
std::any::TypeId::of::<$name>()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn $crate::Action> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
$from_json_fn
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -33,3 +33,6 @@ pub use platform::{Event, NavigationDirection, PathPromptOptions, Platform, Prom
|
||||
pub use presenter::{
|
||||
Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
|
||||
};
|
||||
|
||||
pub use anyhow;
|
||||
pub use serde_json;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::Action;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::{HashMap, HashSet},
|
||||
@ -106,6 +106,11 @@ impl Matcher {
|
||||
self.keymap.add_bindings(bindings);
|
||||
}
|
||||
|
||||
pub fn clear_bindings(&mut self) {
|
||||
self.pending.clear();
|
||||
self.keymap.clear();
|
||||
}
|
||||
|
||||
pub fn clear_pending(&mut self) {
|
||||
self.pending.clear();
|
||||
}
|
||||
@ -164,24 +169,34 @@ impl Keymap {
|
||||
fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
|
||||
self.0.extend(bindings.into_iter());
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Binding {
|
||||
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
|
||||
Self::load(keystrokes, Box::new(action), context).unwrap()
|
||||
}
|
||||
|
||||
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||
let context = if let Some(context) = context {
|
||||
Some(ContextPredicate::parse(context).unwrap())
|
||||
Some(ContextPredicate::parse(context)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
keystrokes: keystrokes
|
||||
.split_whitespace()
|
||||
.map(|key| Keystroke::parse(key).unwrap())
|
||||
.collect(),
|
||||
action: Box::new(action),
|
||||
let keystrokes = keystrokes
|
||||
.split_whitespace()
|
||||
.map(|key| Keystroke::parse(key))
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
Ok(Self {
|
||||
keystrokes,
|
||||
action,
|
||||
context,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,6 +343,8 @@ impl ContextPredicate {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{actions, impl_actions};
|
||||
|
||||
use super::*;
|
||||
@ -419,30 +436,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_matcher() -> anyhow::Result<()> {
|
||||
#[derive(Clone)]
|
||||
pub struct A(pub &'static str);
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct A(pub String);
|
||||
impl_actions!(test, [A]);
|
||||
actions!(test, [B, Ab]);
|
||||
|
||||
impl PartialEq for A {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
impl Eq for A {}
|
||||
impl Debug for A {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "A({:?})", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct ActionArg {
|
||||
a: &'static str,
|
||||
}
|
||||
|
||||
let keymap = Keymap(vec![
|
||||
Binding::new("a", A("x"), Some("a")),
|
||||
Binding::new("a", A("x".to_string()), Some("a")),
|
||||
Binding::new("b", B, Some("a")),
|
||||
Binding::new("a b", Ab, Some("a || b")),
|
||||
]);
|
||||
@ -456,40 +461,54 @@ mod tests {
|
||||
let mut matcher = Matcher::new(keymap);
|
||||
|
||||
// Basic match
|
||||
assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
|
||||
assert_eq!(
|
||||
downcast(&matcher.test_keystroke("a", 1, &ctx_a)),
|
||||
Some(&A("x".to_string()))
|
||||
);
|
||||
|
||||
// Multi-keystroke match
|
||||
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
|
||||
assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
|
||||
assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none());
|
||||
assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab));
|
||||
|
||||
// Failed matches don't interfere with matching subsequent keys
|
||||
assert_eq!(matcher.test_keystroke::<A>("x", 1, &ctx_a), None);
|
||||
assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
|
||||
assert!(matcher.test_keystroke("x", 1, &ctx_a).is_none());
|
||||
assert_eq!(
|
||||
downcast(&matcher.test_keystroke("a", 1, &ctx_a)),
|
||||
Some(&A("x".to_string()))
|
||||
);
|
||||
|
||||
// Pending keystrokes are cleared when the context changes
|
||||
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
|
||||
assert_eq!(matcher.test_keystroke("b", 1, &ctx_a), Some(B));
|
||||
assert!(&matcher.test_keystroke("a", 1, &ctx_b).is_none());
|
||||
assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_a)), Some(&B));
|
||||
|
||||
let mut ctx_c = Context::default();
|
||||
ctx_c.set.insert("c".into());
|
||||
|
||||
// Pending keystrokes are maintained per-view
|
||||
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
|
||||
assert_eq!(matcher.test_keystroke::<A>("a", 2, &ctx_c), None);
|
||||
assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
|
||||
assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none());
|
||||
assert!(matcher.test_keystroke("a", 2, &ctx_c).is_none());
|
||||
assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn downcast<'a, A: Action>(action: &'a Option<Box<dyn Action>>) -> Option<&'a A> {
|
||||
action
|
||||
.as_ref()
|
||||
.and_then(|action| action.as_any().downcast_ref())
|
||||
}
|
||||
|
||||
impl Matcher {
|
||||
fn test_keystroke<A>(&mut self, keystroke: &str, view_id: usize, cx: &Context) -> Option<A>
|
||||
where
|
||||
A: Action + Debug + Eq,
|
||||
{
|
||||
fn test_keystroke(
|
||||
&mut self,
|
||||
keystroke: &str,
|
||||
view_id: usize,
|
||||
cx: &Context,
|
||||
) -> Option<Box<dyn Action>> {
|
||||
if let MatchResult::Action(action) =
|
||||
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
|
||||
{
|
||||
Some(*action.boxed_clone_as_any().downcast().unwrap())
|
||||
Some(action.boxed_clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ use crate::geometry::{
|
||||
rect::RectI,
|
||||
vector::{vec2i, Vector2I},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use foreign_types::ForeignType;
|
||||
use log::warn;
|
||||
use metal::{Device, TextureDescriptor};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
@ -41,36 +41,40 @@ impl AtlasAllocator {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, requested_size: Vector2I) -> (AllocId, Vector2I) {
|
||||
let (alloc_id, origin) = self
|
||||
pub fn allocate(&mut self, requested_size: Vector2I) -> Option<(AllocId, Vector2I)> {
|
||||
let allocation = self
|
||||
.atlases
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.allocate(requested_size)
|
||||
.unwrap_or_else(|| {
|
||||
.or_else(|| {
|
||||
let mut atlas = self.new_atlas(requested_size);
|
||||
let (id, origin) = atlas
|
||||
.allocate(requested_size)
|
||||
.ok_or_else(|| {
|
||||
anyhow!("could not allocate requested size {:?}", requested_size)
|
||||
})
|
||||
.unwrap();
|
||||
let (id, origin) = atlas.allocate(requested_size)?;
|
||||
self.atlases.push(atlas);
|
||||
(id, origin)
|
||||
Some((id, origin))
|
||||
});
|
||||
|
||||
if allocation.is_none() {
|
||||
warn!(
|
||||
"allocation of size {:?} could not be created",
|
||||
requested_size,
|
||||
);
|
||||
}
|
||||
|
||||
let (alloc_id, origin) = allocation?;
|
||||
|
||||
let id = AllocId {
|
||||
atlas_id: self.atlases.len() - 1,
|
||||
alloc_id,
|
||||
};
|
||||
(id, origin)
|
||||
Some((id, origin))
|
||||
}
|
||||
|
||||
pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> (AllocId, RectI) {
|
||||
let (alloc_id, origin) = self.allocate(size);
|
||||
pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> Option<(AllocId, RectI)> {
|
||||
let (alloc_id, origin) = self.allocate(size)?;
|
||||
let bounds = RectI::new(origin, size);
|
||||
self.atlases[alloc_id.atlas_id].upload(bounds, bytes);
|
||||
(alloc_id, bounds)
|
||||
Some((alloc_id, bounds))
|
||||
}
|
||||
|
||||
pub fn deallocate(&mut self, id: AllocId) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use anyhow::anyhow;
|
||||
use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
|
||||
|
||||
use super::atlas::{AllocId, AtlasAllocator};
|
||||
@ -31,7 +32,9 @@ impl ImageCache {
|
||||
.prev_frame
|
||||
.remove(&image.id)
|
||||
.or_else(|| self.curr_frame.get(&image.id).copied())
|
||||
.unwrap_or_else(|| self.atlases.upload(image.size(), image.as_bytes()));
|
||||
.or_else(|| self.atlases.upload(image.size(), image.as_bytes()))
|
||||
.ok_or_else(|| anyhow!("could not upload image of size {:?}", image.size()))
|
||||
.unwrap();
|
||||
self.curr_frame.insert(image.id, (alloc_id, atlas_bounds));
|
||||
(alloc_id, atlas_bounds)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow, Underline},
|
||||
};
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use log::warn;
|
||||
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use shaders::ToFloat2 as _;
|
||||
use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, sync::Arc, vec};
|
||||
@ -172,7 +173,14 @@ impl Renderer {
|
||||
for path in layer.paths() {
|
||||
let origin = path.bounds.origin() * scene.scale_factor();
|
||||
let size = (path.bounds.size() * scene.scale_factor()).ceil();
|
||||
let (alloc_id, atlas_origin) = self.path_atlases.allocate(size.to_i32());
|
||||
|
||||
let path_allocation = self.path_atlases.allocate(size.to_i32());
|
||||
if path_allocation.is_none() {
|
||||
// Path size was likely zero.
|
||||
warn!("could not allocate path texture of size {:?}", size);
|
||||
continue;
|
||||
}
|
||||
let (alloc_id, atlas_origin) = path_allocation.unwrap();
|
||||
let atlas_origin = atlas_origin.to_f32();
|
||||
sprites.push(PathSprite {
|
||||
layer_id,
|
||||
@ -569,6 +577,10 @@ impl Renderer {
|
||||
let sprite =
|
||||
self.sprite_cache
|
||||
.render_icon(source_size, icon.path.clone(), icon.svg.clone());
|
||||
if sprite.is_none() {
|
||||
continue;
|
||||
}
|
||||
let sprite = sprite.unwrap();
|
||||
|
||||
sprites_by_atlas
|
||||
.entry(sprite.atlas_id)
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
geometry::vector::{vec2f, Vector2F, Vector2I},
|
||||
platform,
|
||||
};
|
||||
use collections::hash_map::Entry;
|
||||
use metal::{MTLPixelFormat, TextureDescriptor};
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
||||
@ -114,7 +115,9 @@ impl SpriteCache {
|
||||
scale_factor,
|
||||
)?;
|
||||
|
||||
let (alloc_id, atlas_bounds) = atlases.upload(glyph_bounds.size(), &mask);
|
||||
let (alloc_id, atlas_bounds) = atlases
|
||||
.upload(glyph_bounds.size(), &mask)
|
||||
.expect("could not upload glyph");
|
||||
Some(GlyphSprite {
|
||||
atlas_id: alloc_id.atlas_id,
|
||||
atlas_origin: atlas_bounds.origin(),
|
||||
@ -130,15 +133,15 @@ impl SpriteCache {
|
||||
size: Vector2I,
|
||||
path: Cow<'static, str>,
|
||||
svg: usvg::Tree,
|
||||
) -> IconSprite {
|
||||
) -> Option<IconSprite> {
|
||||
let atlases = &mut self.atlases;
|
||||
self.icons
|
||||
.entry(IconDescriptor {
|
||||
path,
|
||||
width: size.x(),
|
||||
height: size.y(),
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
match self.icons.entry(IconDescriptor {
|
||||
path,
|
||||
width: size.x(),
|
||||
height: size.y(),
|
||||
}) {
|
||||
Entry::Occupied(entry) => Some(entry.get().clone()),
|
||||
Entry::Vacant(entry) => {
|
||||
let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap();
|
||||
resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
|
||||
let mask = pixmap
|
||||
@ -146,15 +149,15 @@ impl SpriteCache {
|
||||
.iter()
|
||||
.map(|a| a.alpha())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (alloc_id, atlas_bounds) = atlases.upload(size, &mask);
|
||||
IconSprite {
|
||||
let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
|
||||
let icon_sprite = IconSprite {
|
||||
atlas_id: alloc_id.atlas_id,
|
||||
atlas_origin: atlas_bounds.origin(),
|
||||
size,
|
||||
}
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
Some(entry.insert(icon_sprite).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
|
||||
|
@ -1,3 +1,5 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
actions, elements::*, impl_actions, AppContext, Entity, MutableAppContext, RenderContext, View,
|
||||
ViewContext, WeakViewHandle,
|
||||
@ -25,7 +27,7 @@ pub enum ItemType {
|
||||
Unselected,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct SelectItem(pub usize);
|
||||
|
||||
actions!(select, [ToggleSelect]);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use chrono::{Datelike, Local, Timelike};
|
||||
use editor::{Autoscroll, Editor};
|
||||
use gpui::{actions, keymap::Binding, MutableAppContext};
|
||||
use gpui::{actions, MutableAppContext};
|
||||
use std::{fs::OpenOptions, sync::Arc};
|
||||
use util::TryFutureExt as _;
|
||||
use workspace::AppState;
|
||||
@ -8,7 +8,6 @@ use workspace::AppState;
|
||||
actions!(journal, [NewJournalEntry]);
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
cx.add_bindings(vec![Binding::new("ctrl-alt-cmd-j", NewJournalEntry, None)]);
|
||||
cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
|
||||
}
|
||||
|
||||
|
@ -234,6 +234,14 @@ impl LanguageRegistry {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn language_names(&self) -> Vec<String> {
|
||||
self.languages
|
||||
.read()
|
||||
.iter()
|
||||
.map(|language| language.name().to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn select_language(&self, path: impl AsRef<Path>) -> Option<Arc<Language>> {
|
||||
let path = path.as_ref();
|
||||
let filename = path.file_name().and_then(|name| name.to_str());
|
||||
|
@ -821,7 +821,10 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
}
|
||||
50..=59 if replica_ids.len() < max_peers => {
|
||||
let old_buffer = buffer.read(cx).to_proto();
|
||||
let new_replica_id = replica_ids.len() as ReplicaId;
|
||||
let new_replica_id = (0..=replica_ids.len() as ReplicaId)
|
||||
.filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
|
||||
.choose(&mut rng)
|
||||
.unwrap();
|
||||
log::info!(
|
||||
"Adding new replica {} (replicating from {})",
|
||||
new_replica_id,
|
||||
@ -830,6 +833,11 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
new_buffer = Some(cx.add_model(|cx| {
|
||||
let mut new_buffer =
|
||||
Buffer::from_proto(new_replica_id, old_buffer, None, cx).unwrap();
|
||||
log::info!(
|
||||
"New replica {} text: {:?}",
|
||||
new_buffer.replica_id(),
|
||||
new_buffer.text()
|
||||
);
|
||||
new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
|
||||
let network = network.clone();
|
||||
cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
|
||||
@ -843,8 +851,33 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
.detach();
|
||||
new_buffer
|
||||
}));
|
||||
replica_ids.push(new_replica_id);
|
||||
network.borrow_mut().replicate(replica_id, new_replica_id);
|
||||
|
||||
if new_replica_id as usize == replica_ids.len() {
|
||||
replica_ids.push(new_replica_id);
|
||||
} else {
|
||||
let new_buffer = new_buffer.take().unwrap();
|
||||
while network.borrow().has_unreceived(new_replica_id) {
|
||||
let ops = network
|
||||
.borrow_mut()
|
||||
.receive(new_replica_id)
|
||||
.into_iter()
|
||||
.map(|op| proto::deserialize_operation(op).unwrap());
|
||||
if ops.len() > 0 {
|
||||
log::info!(
|
||||
"peer {} (version: {:?}) applying {} ops from the network. {:?}",
|
||||
new_replica_id,
|
||||
buffer.read(cx).version(),
|
||||
ops.len(),
|
||||
ops
|
||||
);
|
||||
new_buffer.update(cx, |new_buffer, cx| {
|
||||
new_buffer.apply_ops(ops, cx).unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
buffers[new_replica_id as usize] = new_buffer;
|
||||
}
|
||||
}
|
||||
60..=69 if mutation_count != 0 => {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
@ -861,9 +894,11 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
.map(|op| proto::deserialize_operation(op).unwrap());
|
||||
if ops.len() > 0 {
|
||||
log::info!(
|
||||
"peer {} applying {} ops from the network.",
|
||||
"peer {} (version: {:?}) applying {} ops from the network. {:?}",
|
||||
replica_id,
|
||||
ops.len()
|
||||
buffer.read(cx).version(),
|
||||
ops.len(),
|
||||
ops
|
||||
);
|
||||
buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
|
||||
}
|
||||
@ -886,6 +921,12 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
let first_buffer = buffers[0].read(cx).snapshot();
|
||||
for buffer in &buffers[1..] {
|
||||
let buffer = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
buffer.version(),
|
||||
first_buffer.version(),
|
||||
"Replica {} version != Replica 0 version",
|
||||
buffer.replica_id()
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
first_buffer.text(),
|
||||
@ -915,7 +956,12 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
.filter(|(replica_id, _)| **replica_id != buffer.replica_id())
|
||||
.map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual_remote_selections, expected_remote_selections);
|
||||
assert_eq!(
|
||||
actual_remote_selections,
|
||||
expected_remote_selections,
|
||||
"Replica {} remote selections != expected selections",
|
||||
buffer.replica_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,8 @@ use editor::{
|
||||
};
|
||||
use fuzzy::StringMatch;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
geometry::vector::Vector2F,
|
||||
keymap::{self, Binding},
|
||||
AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
actions, elements::*, geometry::vector::Vector2F, keymap, AppContext, Axis, Entity,
|
||||
MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::Outline;
|
||||
use ordered_float::OrderedFloat;
|
||||
@ -23,10 +19,6 @@ use workspace::{
|
||||
actions!(outline, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings([
|
||||
Binding::new("cmd-shift-O", Toggle, Some("Editor")),
|
||||
Binding::new("escape", Toggle, Some("OutlineView")),
|
||||
]);
|
||||
cx.add_action(OutlineView::toggle);
|
||||
cx.add_action(OutlineView::confirm);
|
||||
cx.add_action(OutlineView::select_prev);
|
||||
|
@ -4,8 +4,7 @@ use gpui::{
|
||||
Align, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget,
|
||||
Svg, UniformList, UniformListState,
|
||||
},
|
||||
impl_actions,
|
||||
keymap::{self, Binding},
|
||||
impl_internal_actions, keymap,
|
||||
platform::CursorStyle,
|
||||
AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
@ -54,7 +53,7 @@ pub struct ToggleExpanded(pub ProjectEntryId);
|
||||
pub struct Open(pub ProjectEntryId);
|
||||
|
||||
actions!(project_panel, [ExpandSelectedEntry, CollapseSelectedEntry]);
|
||||
impl_actions!(project_panel, [Open, ToggleExpanded]);
|
||||
impl_internal_actions!(project_panel, [Open, ToggleExpanded]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ProjectPanel::expand_selected_entry);
|
||||
@ -63,10 +62,6 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ProjectPanel::select_prev);
|
||||
cx.add_action(ProjectPanel::select_next);
|
||||
cx.add_action(ProjectPanel::open_entry);
|
||||
cx.add_bindings([
|
||||
Binding::new("right", ExpandSelectedEntry, Some("ProjectPanel")),
|
||||
Binding::new("left", CollapseSelectedEntry, Some("ProjectPanel")),
|
||||
]);
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
|
@ -3,11 +3,8 @@ use editor::{
|
||||
};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
keymap::{self, Binding},
|
||||
AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
actions, elements::*, keymap, AppContext, Axis, Entity, ModelHandle, MutableAppContext,
|
||||
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{Project, Symbol};
|
||||
@ -25,10 +22,6 @@ use workspace::{
|
||||
actions!(project_symbols, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings([
|
||||
Binding::new("cmd-t", Toggle, None),
|
||||
Binding::new("escape", Toggle, Some("ProjectSymbolsView")),
|
||||
]);
|
||||
cx.add_action(ProjectSymbolsView::toggle);
|
||||
cx.add_action(ProjectSymbolsView::confirm);
|
||||
cx.add_action(ProjectSymbolsView::select_prev);
|
||||
|
@ -20,6 +20,7 @@ workspace = { path = "../workspace" }
|
||||
anyhow = "1.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
@ -1,55 +1,47 @@
|
||||
use crate::{active_match_index, match_index_for_direction, Direction, SearchOption, SelectMatch};
|
||||
use crate::{
|
||||
active_match_index, match_index_for_direction, Direction, SearchOption, SelectNextMatch,
|
||||
SelectPrevMatch,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::{display_map::ToDisplayPoint, Anchor, Autoscroll, Bias, Editor};
|
||||
use gpui::{
|
||||
actions, elements::*, impl_actions, keymap::Binding, platform::CursorStyle, AppContext, Entity,
|
||||
MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
|
||||
actions, elements::*, impl_actions, impl_internal_actions, platform::CursorStyle, AppContext,
|
||||
Entity, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use language::OffsetRangeExt;
|
||||
use project::search::SearchQuery;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::ops::Range;
|
||||
use workspace::{ItemHandle, Pane, ToolbarItemLocation, ToolbarItemView};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Deploy(pub bool);
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Deploy {
|
||||
pub focus: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToggleSearchOption(pub SearchOption);
|
||||
|
||||
actions!(buffer_search, [Dismiss, FocusEditor]);
|
||||
impl_actions!(buffer_search, [Deploy, ToggleSearchOption]);
|
||||
impl_actions!(buffer_search, [Deploy]);
|
||||
impl_internal_actions!(buffer_search, [ToggleSearchOption]);
|
||||
|
||||
pub enum Event {
|
||||
UpdateLocation,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings([
|
||||
Binding::new("cmd-f", Deploy(true), Some("Editor && mode == full")),
|
||||
Binding::new("cmd-e", Deploy(false), Some("Editor && mode == full")),
|
||||
Binding::new("escape", Dismiss, Some("BufferSearchBar")),
|
||||
Binding::new("cmd-f", FocusEditor, Some("BufferSearchBar")),
|
||||
Binding::new(
|
||||
"enter",
|
||||
SelectMatch(Direction::Next),
|
||||
Some("BufferSearchBar"),
|
||||
),
|
||||
Binding::new(
|
||||
"shift-enter",
|
||||
SelectMatch(Direction::Prev),
|
||||
Some("BufferSearchBar"),
|
||||
),
|
||||
Binding::new("cmd-g", SelectMatch(Direction::Next), Some("Pane")),
|
||||
Binding::new("cmd-shift-G", SelectMatch(Direction::Prev), Some("Pane")),
|
||||
]);
|
||||
cx.add_action(BufferSearchBar::deploy);
|
||||
cx.add_action(BufferSearchBar::dismiss);
|
||||
cx.add_action(BufferSearchBar::focus_editor);
|
||||
cx.add_action(BufferSearchBar::toggle_search_option);
|
||||
cx.add_action(BufferSearchBar::select_match);
|
||||
cx.add_action(BufferSearchBar::select_match_on_pane);
|
||||
cx.add_action(BufferSearchBar::select_next_match);
|
||||
cx.add_action(BufferSearchBar::select_prev_match);
|
||||
cx.add_action(BufferSearchBar::select_next_match_on_pane);
|
||||
cx.add_action(BufferSearchBar::select_prev_match_on_pane);
|
||||
cx.add_action(BufferSearchBar::handle_editor_cancel);
|
||||
}
|
||||
|
||||
pub struct BufferSearchBar {
|
||||
@ -325,14 +317,27 @@ impl BufferSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| cx.dispatch_action(SelectMatch(direction)))
|
||||
.on_click(move |cx| match direction {
|
||||
Direction::Prev => cx.dispatch_action(SelectPrevMatch),
|
||||
Direction::Next => cx.dispatch_action(SelectNextMatch),
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn deploy(pane: &mut Pane, Deploy(focus): &Deploy, cx: &mut ViewContext<Pane>) {
|
||||
fn deploy(pane: &mut Pane, action: &Deploy, cx: &mut ViewContext<Pane>) {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
if search_bar.update(cx, |search_bar, cx| search_bar.show(*focus, cx)) {
|
||||
if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, cx)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
fn handle_editor_cancel(pane: &mut Pane, _: &editor::Cancel, cx: &mut ViewContext<Pane>) {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
if !search_bar.read(cx).dismissed {
|
||||
search_bar.update(cx, |search_bar, cx| search_bar.dismiss(&Dismiss, cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -368,7 +373,15 @@ impl BufferSearchBar {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_match(&mut self, &SelectMatch(direction): &SelectMatch, cx: &mut ViewContext<Self>) {
|
||||
fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext<Self>) {
|
||||
self.select_match(Direction::Next, cx);
|
||||
}
|
||||
|
||||
fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext<Self>) {
|
||||
self.select_match(Direction::Prev, cx);
|
||||
}
|
||||
|
||||
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||
if let Some(index) = self.active_match_index {
|
||||
if let Some(editor) = self.active_editor.as_ref() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
@ -389,9 +402,23 @@ impl BufferSearchBar {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_match_on_pane(pane: &mut Pane, action: &SelectMatch, cx: &mut ViewContext<Pane>) {
|
||||
fn select_next_match_on_pane(
|
||||
pane: &mut Pane,
|
||||
action: &SelectNextMatch,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |search_bar, cx| search_bar.select_match(action, cx));
|
||||
search_bar.update(cx, |bar, cx| bar.select_next_match(action, cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev_match_on_pane(
|
||||
pane: &mut Pane,
|
||||
action: &SelectPrevMatch,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.select_prev_match(action, cx));
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,7 +726,7 @@ mod tests {
|
||||
});
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
assert_eq!(search_bar.active_match_index, Some(0));
|
||||
search_bar.select_match(&SelectMatch(Direction::Next), cx);
|
||||
search_bar.select_next_match(&SelectNextMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
|
||||
@ -710,7 +737,7 @@ mod tests {
|
||||
});
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_match(&SelectMatch(Direction::Next), cx);
|
||||
search_bar.select_next_match(&SelectNextMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
|
||||
@ -721,7 +748,7 @@ mod tests {
|
||||
});
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_match(&SelectMatch(Direction::Next), cx);
|
||||
search_bar.select_next_match(&SelectNextMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
|
||||
@ -732,7 +759,7 @@ mod tests {
|
||||
});
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_match(&SelectMatch(Direction::Next), cx);
|
||||
search_bar.select_next_match(&SelectNextMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
|
||||
@ -743,7 +770,7 @@ mod tests {
|
||||
});
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_match(&SelectMatch(Direction::Prev), cx);
|
||||
search_bar.select_prev_match(&SelectPrevMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
|
||||
@ -754,7 +781,7 @@ mod tests {
|
||||
});
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_match(&SelectMatch(Direction::Prev), cx);
|
||||
search_bar.select_prev_match(&SelectPrevMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
|
||||
@ -765,7 +792,7 @@ mod tests {
|
||||
});
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_match(&SelectMatch(Direction::Prev), cx);
|
||||
search_bar.select_prev_match(&SelectPrevMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
|
||||
@ -782,7 +809,7 @@ mod tests {
|
||||
});
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
assert_eq!(search_bar.active_match_index, Some(1));
|
||||
search_bar.select_match(&SelectMatch(Direction::Prev), cx);
|
||||
search_bar.select_prev_match(&SelectPrevMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
|
||||
@ -799,7 +826,7 @@ mod tests {
|
||||
});
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
assert_eq!(search_bar.active_match_index, Some(1));
|
||||
search_bar.select_match(&SelectMatch(Direction::Next), cx);
|
||||
search_bar.select_next_match(&SelectNextMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
|
||||
@ -816,7 +843,7 @@ mod tests {
|
||||
});
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
assert_eq!(search_bar.active_match_index, Some(2));
|
||||
search_bar.select_match(&SelectMatch(Direction::Prev), cx);
|
||||
search_bar.select_prev_match(&SelectPrevMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
|
||||
@ -833,7 +860,7 @@ mod tests {
|
||||
});
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
assert_eq!(search_bar.active_match_index, Some(2));
|
||||
search_bar.select_match(&SelectMatch(Direction::Next), cx);
|
||||
search_bar.select_next_match(&SelectNextMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
|
||||
@ -850,7 +877,7 @@ mod tests {
|
||||
});
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
assert_eq!(search_bar.active_match_index, Some(0));
|
||||
search_bar.select_match(&SelectMatch(Direction::Prev), cx);
|
||||
search_bar.select_prev_match(&SelectPrevMatch, cx);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)),
|
||||
[DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::{
|
||||
active_match_index, match_index_for_direction, Direction, SearchOption, SelectMatch,
|
||||
ToggleSearchOption,
|
||||
active_match_index, match_index_for_direction, Direction, SearchOption, SelectNextMatch,
|
||||
SelectPrevMatch, ToggleSearchOption,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll};
|
||||
use gpui::{
|
||||
actions, elements::*, keymap::Binding, platform::CursorStyle, AppContext, ElementBox, Entity,
|
||||
ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
|
||||
ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
|
||||
actions, elements::*, platform::CursorStyle, AppContext, ElementBox, Entity, ModelContext,
|
||||
ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext,
|
||||
ViewHandle, WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
use project::{search::SearchQuery, Project};
|
||||
use settings::Settings;
|
||||
@ -28,20 +28,12 @@ struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSe
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.set_global(ActiveSearches::default());
|
||||
cx.add_bindings([
|
||||
Binding::new("cmd-shift-F", ToggleFocus, Some("Pane")),
|
||||
Binding::new("cmd-f", ToggleFocus, Some("Pane")),
|
||||
Binding::new("cmd-shift-F", Deploy, Some("Workspace")),
|
||||
Binding::new("enter", Search, Some("ProjectSearchBar")),
|
||||
Binding::new("cmd-enter", SearchInNew, Some("ProjectSearchBar")),
|
||||
Binding::new("cmd-g", SelectMatch(Direction::Next), Some("Pane")),
|
||||
Binding::new("cmd-shift-G", SelectMatch(Direction::Prev), Some("Pane")),
|
||||
]);
|
||||
cx.add_action(ProjectSearchView::deploy);
|
||||
cx.add_action(ProjectSearchBar::search);
|
||||
cx.add_action(ProjectSearchBar::search_in_new);
|
||||
cx.add_action(ProjectSearchBar::toggle_search_option);
|
||||
cx.add_action(ProjectSearchBar::select_match);
|
||||
cx.add_action(ProjectSearchBar::select_next_match);
|
||||
cx.add_action(ProjectSearchBar::select_prev_match);
|
||||
cx.add_action(ProjectSearchBar::toggle_focus);
|
||||
cx.capture_action(ProjectSearchBar::tab);
|
||||
}
|
||||
@ -136,6 +128,7 @@ impl ProjectSearch {
|
||||
|
||||
pub enum ViewEvent {
|
||||
UpdateTab,
|
||||
EditorEvent(editor::Event),
|
||||
}
|
||||
|
||||
impl Entity for ProjectSearchView {
|
||||
@ -177,11 +170,7 @@ impl View for ProjectSearchView {
|
||||
.insert(self.model.read(cx).project.downgrade(), handle)
|
||||
});
|
||||
|
||||
if self.model.read(cx).match_ranges.is_empty() {
|
||||
cx.focus(&self.query_editor);
|
||||
} else {
|
||||
self.focus_results_editor(cx);
|
||||
}
|
||||
self.focus_query_editor(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,6 +293,14 @@ impl Item for ProjectSearchView {
|
||||
.update(cx, |editor, cx| editor.navigate(data, cx))
|
||||
}
|
||||
|
||||
fn should_activate_item_on_event(event: &Self::Event) -> bool {
|
||||
if let ViewEvent::EditorEvent(editor_event) = event {
|
||||
Editor::should_activate_item_on_event(editor_event)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn should_update_tab_on_event(event: &ViewEvent) -> bool {
|
||||
matches!(event, ViewEvent::UpdateTab)
|
||||
}
|
||||
@ -338,6 +335,11 @@ impl ProjectSearchView {
|
||||
editor.set_text(query_text, cx);
|
||||
editor
|
||||
});
|
||||
// Subcribe to query_editor in order to reraise editor events for workspace item activation purposes
|
||||
cx.subscribe(&query_editor, |_, _, event, cx| {
|
||||
cx.emit(ViewEvent::EditorEvent(event.clone()))
|
||||
})
|
||||
.detach();
|
||||
|
||||
let results_editor = cx.add_view(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(excerpts, Some(project), cx);
|
||||
@ -350,6 +352,8 @@ impl ProjectSearchView {
|
||||
if matches!(event, editor::Event::SelectionsChanged { .. }) {
|
||||
this.update_match_index(cx);
|
||||
}
|
||||
// Reraise editor events for workspace item activation purposes
|
||||
cx.emit(ViewEvent::EditorEvent(event.clone()));
|
||||
})
|
||||
.detach();
|
||||
|
||||
@ -390,6 +394,7 @@ impl ProjectSearchView {
|
||||
|
||||
if let Some(existing) = existing {
|
||||
workspace.activate_item(&existing, cx);
|
||||
existing.update(cx, |existing, cx| existing.focus_query_editor(cx));
|
||||
} else {
|
||||
let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||
workspace.add_item(
|
||||
@ -545,18 +550,23 @@ impl ProjectSearchBar {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_match(
|
||||
pane: &mut Pane,
|
||||
&SelectMatch(direction): &SelectMatch,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) {
|
||||
fn select_next_match(pane: &mut Pane, _: &SelectNextMatch, cx: &mut ViewContext<Pane>) {
|
||||
if let Some(search_view) = pane
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||
{
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view.select_match(direction, cx);
|
||||
});
|
||||
search_view.update(cx, |view, cx| view.select_match(Direction::Next, cx));
|
||||
} else {
|
||||
cx.propagate_action();
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev_match(pane: &mut Pane, _: &SelectPrevMatch, cx: &mut ViewContext<Pane>) {
|
||||
if let Some(search_view) = pane
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||
{
|
||||
search_view.update(cx, |view, cx| view.select_match(Direction::Prev, cx));
|
||||
} else {
|
||||
cx.propagate_action();
|
||||
}
|
||||
@ -635,7 +645,10 @@ impl ProjectSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| cx.dispatch_action(SelectMatch(direction)))
|
||||
.on_click(move |cx| match direction {
|
||||
Direction::Prev => cx.dispatch_action(SelectPrevMatch),
|
||||
Direction::Next => cx.dispatch_action(SelectNextMatch),
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
pub use buffer_search::BufferSearchBar;
|
||||
use editor::{Anchor, MultiBufferSnapshot};
|
||||
use gpui::{impl_actions, MutableAppContext};
|
||||
use gpui::{actions, impl_internal_actions, MutableAppContext};
|
||||
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
@ -18,10 +18,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
#[derive(Clone)]
|
||||
pub struct ToggleSearchOption(pub SearchOption);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SelectMatch(pub Direction);
|
||||
|
||||
impl_actions!(search, [ToggleSearchOption, SelectMatch]);
|
||||
actions!(search, [SelectNextMatch, SelectPrevMatch]);
|
||||
impl_internal_actions!(search, [ToggleSearchOption]);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum SearchOption {
|
||||
|
@ -11,6 +11,8 @@ doctest = false
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
assets = { path = "../assets" }
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
62
crates/settings/src/keymap_file.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use anyhow::{Context, Result};
|
||||
use assets::Assets;
|
||||
use collections::BTreeMap;
|
||||
use gpui::{keymap::Binding, MutableAppContext};
|
||||
use serde::Deserialize;
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
#[derive(Deserialize, Default, Clone)]
|
||||
#[serde(transparent)]
|
||||
pub struct KeymapFile(BTreeMap<String, ActionsByKeystroke>);
|
||||
|
||||
type ActionsByKeystroke = BTreeMap<String, Box<RawValue>>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActionWithData<'a>(#[serde(borrow)] &'a str, #[serde(borrow)] &'a RawValue);
|
||||
|
||||
impl KeymapFile {
|
||||
pub fn load_defaults(cx: &mut MutableAppContext) {
|
||||
for path in ["keymaps/default.json", "keymaps/vim.json"] {
|
||||
Self::load(path, cx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(asset_path: &str, cx: &mut MutableAppContext) -> Result<()> {
|
||||
let content = Assets::get(asset_path).unwrap().data;
|
||||
let content_str = std::str::from_utf8(content.as_ref()).unwrap();
|
||||
Ok(serde_json::from_str::<Self>(content_str)?.add(cx)?)
|
||||
}
|
||||
|
||||
pub fn add(self, cx: &mut MutableAppContext) -> Result<()> {
|
||||
for (context, actions) in self.0 {
|
||||
let context = if context == "*" { None } else { Some(context) };
|
||||
cx.add_bindings(
|
||||
actions
|
||||
.into_iter()
|
||||
.map(|(keystroke, action)| {
|
||||
let action = action.get();
|
||||
|
||||
// This is a workaround for a limitation in serde: serde-rs/json#497
|
||||
// We want to deserialize the action data as a `RawValue` so that we can
|
||||
// deserialize the action itself dynamically directly from the JSON
|
||||
// string. But `RawValue` currently does not work inside of an untagged enum.
|
||||
let action = if action.starts_with('[') {
|
||||
let ActionWithData(name, data) = serde_json::from_str(action)?;
|
||||
cx.deserialize_action(name, Some(data.get()))
|
||||
} else {
|
||||
let name = serde_json::from_str(action)?;
|
||||
cx.deserialize_action(name, None)
|
||||
}
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"invalid binding value for keystroke {keystroke}, context {context:?}"
|
||||
)
|
||||
})?;
|
||||
Binding::load(&keystroke, action, context.as_deref())
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,11 +1,22 @@
|
||||
mod keymap_file;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::font_cache::{FamilyId, FontCache};
|
||||
use schemars::{schema_for, JsonSchema};
|
||||
use schemars::{
|
||||
gen::{SchemaGenerator, SchemaSettings},
|
||||
schema::{
|
||||
InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec, SubschemaValidation,
|
||||
},
|
||||
JsonSchema,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use theme::{Theme, ThemeRegistry};
|
||||
use util::ResultExt as _;
|
||||
|
||||
pub use keymap_file::KeymapFile;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Settings {
|
||||
pub buffer_font_family: FamilyId,
|
||||
@ -67,8 +78,88 @@ impl Settings {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn file_json_schema() -> serde_json::Value {
|
||||
serde_json::to_value(schema_for!(SettingsFileContent)).unwrap()
|
||||
pub fn file_json_schema(
|
||||
theme_names: Vec<String>,
|
||||
language_names: Vec<String>,
|
||||
) -> serde_json::Value {
|
||||
let settings = SchemaSettings::draft07().with(|settings| {
|
||||
settings.option_add_null_type = false;
|
||||
});
|
||||
let generator = SchemaGenerator::new(settings);
|
||||
let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
|
||||
|
||||
// Construct theme names reference type
|
||||
let theme_names = theme_names
|
||||
.into_iter()
|
||||
.map(|name| Value::String(name))
|
||||
.collect();
|
||||
let theme_names_schema = Schema::Object(SchemaObject {
|
||||
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
||||
enum_values: Some(theme_names),
|
||||
..Default::default()
|
||||
});
|
||||
root_schema
|
||||
.definitions
|
||||
.insert("ThemeName".to_owned(), theme_names_schema);
|
||||
|
||||
// Construct language overrides reference type
|
||||
let language_override_schema_reference = Schema::Object(SchemaObject {
|
||||
reference: Some("#/definitions/LanguageOverride".to_owned()),
|
||||
..Default::default()
|
||||
});
|
||||
let language_overrides_properties = language_names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
(
|
||||
name,
|
||||
Schema::Object(SchemaObject {
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
all_of: Some(vec![language_override_schema_reference.clone()]),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let language_overrides_schema = Schema::Object(SchemaObject {
|
||||
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
properties: language_overrides_properties,
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
});
|
||||
root_schema
|
||||
.definitions
|
||||
.insert("LanguageOverrides".to_owned(), language_overrides_schema);
|
||||
|
||||
// Modify theme property to use new theme reference type
|
||||
let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
|
||||
let language_overrides_schema_reference = Schema::Object(SchemaObject {
|
||||
reference: Some("#/definitions/ThemeName".to_owned()),
|
||||
..Default::default()
|
||||
});
|
||||
settings_file_schema.properties.insert(
|
||||
"theme".to_owned(),
|
||||
Schema::Object(SchemaObject {
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
all_of: Some(vec![language_overrides_schema_reference]),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
|
||||
// Modify language_overrides property to use LanguageOverrides reference
|
||||
settings_file_schema.properties.insert(
|
||||
"language_overrides".to_owned(),
|
||||
Schema::Object(SchemaObject {
|
||||
reference: Some("#/definitions/LanguageOverrides".to_owned()),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
serde_json::to_value(root_schema).unwrap()
|
||||
}
|
||||
|
||||
pub fn with_overrides(
|
||||
@ -128,7 +219,7 @@ impl Settings {
|
||||
}
|
||||
}
|
||||
if let Some(value) = &data.theme {
|
||||
if let Some(theme) = theme_registry.get(value).log_err() {
|
||||
if let Some(theme) = theme_registry.get(&value.to_string()).log_err() {
|
||||
self.theme = theme;
|
||||
}
|
||||
}
|
||||
@ -142,10 +233,10 @@ impl Settings {
|
||||
data.editor.preferred_line_length,
|
||||
);
|
||||
|
||||
for (language_name, settings) in &data.language_overrides {
|
||||
for (language_name, settings) in data.language_overrides.clone().into_iter() {
|
||||
let target = self
|
||||
.language_overrides
|
||||
.entry(language_name.clone())
|
||||
.entry(language_name.into())
|
||||
.or_default();
|
||||
|
||||
merge_option(&mut target.tab_size, settings.tab_size);
|
||||
|
@ -826,6 +826,8 @@ impl Buffer {
|
||||
edit.timestamp,
|
||||
);
|
||||
self.snapshot.version.observe(edit.timestamp.local());
|
||||
self.local_clock.observe(edit.timestamp.local());
|
||||
self.lamport_clock.observe(edit.timestamp.lamport());
|
||||
self.resolve_edit(edit.timestamp.local());
|
||||
}
|
||||
}
|
||||
@ -836,6 +838,7 @@ impl Buffer {
|
||||
if !self.version.observed(undo.id) {
|
||||
self.apply_undo(&undo)?;
|
||||
self.snapshot.version.observe(undo.id);
|
||||
self.local_clock.observe(undo.id);
|
||||
self.lamport_clock.observe(lamport_timestamp);
|
||||
}
|
||||
}
|
||||
@ -1033,8 +1036,6 @@ impl Buffer {
|
||||
self.snapshot.visible_text = visible_text;
|
||||
self.snapshot.deleted_text = deleted_text;
|
||||
self.snapshot.insertions.edit(new_insertions, &());
|
||||
self.local_clock.observe(timestamp.local());
|
||||
self.lamport_clock.observe(timestamp.lamport());
|
||||
self.subscriptions.publish_mut(&edits);
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
use editor::Editor;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
elements::*,
|
||||
impl_actions,
|
||||
keymap::{self, Binding},
|
||||
AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
|
||||
ViewContext, ViewHandle,
|
||||
actions, elements::*, keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext,
|
||||
RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::{cmp, sync::Arc};
|
||||
@ -25,26 +22,14 @@ pub struct ThemeSelector {
|
||||
selection_completed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Toggle(pub Arc<ThemeRegistry>);
|
||||
actions!(theme_selector, [Toggle, Reload]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Reload(pub Arc<ThemeRegistry>);
|
||||
|
||||
impl_actions!(theme_selector, [Toggle, Reload]);
|
||||
|
||||
pub fn init(themes: Arc<ThemeRegistry>, cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ThemeSelector::confirm);
|
||||
cx.add_action(ThemeSelector::select_prev);
|
||||
cx.add_action(ThemeSelector::select_next);
|
||||
cx.add_action(ThemeSelector::toggle);
|
||||
cx.add_action(ThemeSelector::reload);
|
||||
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("cmd-k cmd-t", Toggle(themes.clone()), None),
|
||||
Binding::new("cmd-k t", Reload(themes.clone()), None),
|
||||
Binding::new("escape", Toggle(themes.clone()), Some("ThemeSelector")),
|
||||
]);
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
@ -79,18 +64,20 @@ impl ThemeSelector {
|
||||
this
|
||||
}
|
||||
|
||||
fn toggle(workspace: &mut Workspace, action: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
let themes = workspace.themes();
|
||||
workspace.toggle_modal(cx, |cx, _| {
|
||||
let selector = cx.add_view(|cx| Self::new(action.0.clone(), cx));
|
||||
let selector = cx.add_view(|cx| Self::new(themes, cx));
|
||||
cx.subscribe(&selector, Self::on_event).detach();
|
||||
selector
|
||||
});
|
||||
}
|
||||
|
||||
fn reload(_: &mut Workspace, action: &Reload, cx: &mut ViewContext<Workspace>) {
|
||||
fn reload(workspace: &mut Workspace, _: &Reload, cx: &mut ViewContext<Workspace>) {
|
||||
let current_theme_name = cx.global::<Settings>().theme.name.clone();
|
||||
action.0.clear();
|
||||
match action.0.get(¤t_theme_name) {
|
||||
let themes = workspace.themes();
|
||||
themes.clear();
|
||||
match themes.get(¤t_theme_name) {
|
||||
Ok(theme) => {
|
||||
Self::set_theme(theme, cx);
|
||||
log::info!("reloaded theme {}", current_theme_name);
|
||||
|
@ -8,10 +8,12 @@ path = "src/vim.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
assets = { path = "../assets" }
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
settings = { path = "../settings" }
|
||||
workspace = { path = "../workspace" }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
|
@ -1,18 +1,12 @@
|
||||
use crate::{mode::Mode, SwitchMode, VimState};
|
||||
use editor::Bias;
|
||||
use gpui::{actions, keymap::Binding, MutableAppContext, ViewContext};
|
||||
use gpui::{actions, MutableAppContext, ViewContext};
|
||||
use language::SelectionGoal;
|
||||
use workspace::Workspace;
|
||||
|
||||
actions!(vim, [NormalBefore]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
let context = Some("Editor && vim_mode == insert");
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("escape", NormalBefore, context),
|
||||
Binding::new("ctrl-c", NormalBefore, context),
|
||||
]);
|
||||
|
||||
cx.add_action(normal_before);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use editor::CursorShape;
|
||||
use gpui::keymap::Context;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||
pub enum Mode {
|
||||
Normal(NormalState),
|
||||
Insert,
|
||||
@ -44,7 +45,7 @@ impl Default for Mode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||
pub enum NormalState {
|
||||
None,
|
||||
GPrefix,
|
||||
|
@ -1,18 +1,19 @@
|
||||
mod g_prefix;
|
||||
|
||||
use crate::{mode::NormalState, Mode, SwitchMode, VimState};
|
||||
use crate::VimState;
|
||||
use editor::{char_kind, movement, Bias};
|
||||
use gpui::{actions, impl_actions, keymap::Binding, MutableAppContext, ViewContext};
|
||||
use gpui::{actions, impl_actions, MutableAppContext, ViewContext};
|
||||
use language::SelectionGoal;
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct MoveToNextWordStart(pub bool);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct MoveToNextWordEnd(pub bool);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct MoveToPreviousWordStart(pub bool);
|
||||
|
||||
impl_actions!(
|
||||
@ -39,26 +40,7 @@ actions!(
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
let context = Some("Editor && vim_mode == normal");
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("i", SwitchMode(Mode::Insert), context),
|
||||
Binding::new("g", SwitchMode(Mode::Normal(NormalState::GPrefix)), context),
|
||||
Binding::new("h", MoveLeft, context),
|
||||
Binding::new("j", MoveDown, context),
|
||||
Binding::new("k", MoveUp, context),
|
||||
Binding::new("l", MoveRight, context),
|
||||
Binding::new("0", MoveToStartOfLine, context),
|
||||
Binding::new("shift-$", MoveToEndOfLine, context),
|
||||
Binding::new("shift-G", MoveToEnd, context),
|
||||
Binding::new("w", MoveToNextWordStart(false), context),
|
||||
Binding::new("shift-W", MoveToNextWordStart(true), context),
|
||||
Binding::new("e", MoveToNextWordEnd(false), context),
|
||||
Binding::new("shift-E", MoveToNextWordEnd(true), context),
|
||||
Binding::new("b", MoveToPreviousWordStart(false), context),
|
||||
Binding::new("shift-B", MoveToPreviousWordStart(true), context),
|
||||
]);
|
||||
g_prefix::init(cx);
|
||||
|
||||
cx.add_action(move_left);
|
||||
cx.add_action(move_down);
|
||||
cx.add_action(move_up);
|
||||
|
@ -1,16 +1,10 @@
|
||||
use crate::{mode::Mode, SwitchMode, VimState};
|
||||
use gpui::{actions, keymap::Binding, MutableAppContext, ViewContext};
|
||||
use gpui::{actions, MutableAppContext, ViewContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
actions!(vim, [MoveToStart]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
let context = Some("Editor && vim_mode == normal && vim_submode == g");
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("g", MoveToStart, context),
|
||||
Binding::new("escape", SwitchMode(Mode::normal()), context),
|
||||
]);
|
||||
|
||||
cx.add_action(move_to_start);
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,13 @@ mod vim_test_context;
|
||||
use collections::HashMap;
|
||||
use editor::{CursorShape, Editor};
|
||||
use gpui::{impl_actions, MutableAppContext, ViewContext, WeakViewHandle};
|
||||
use serde::Deserialize;
|
||||
|
||||
use mode::Mode;
|
||||
use settings::Settings;
|
||||
use workspace::{self, Workspace};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct SwitchMode(pub Mode);
|
||||
|
||||
impl_actions!(vim, [SwitchMode]);
|
||||
|
@ -23,7 +23,10 @@ impl<'a> VimTestContext<'a> {
|
||||
cx.update(|cx| {
|
||||
editor::init(cx);
|
||||
crate::init(cx);
|
||||
|
||||
settings::KeymapFile::load("keymaps/vim.json", cx).unwrap();
|
||||
});
|
||||
|
||||
let params = cx.update(WorkspaceParams::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
|
@ -8,7 +8,7 @@ path = "src/workspace.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["client/test-support", "project/test-support"]
|
||||
test-support = ["client/test-support", "project/test-support", "settings/test-support"]
|
||||
|
||||
[dependencies]
|
||||
client = { path = "../client" }
|
||||
|
@ -1,18 +1,4 @@
|
||||
use gpui::{actions, keymap::Binding, MutableAppContext};
|
||||
|
||||
actions!(
|
||||
gpui::actions!(
|
||||
menu,
|
||||
[Confirm, SelectPrev, SelectNext, SelectFirst, SelectLast,]
|
||||
[Confirm, SelectPrev, SelectNext, SelectFirst, SelectLast]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_bindings([
|
||||
Binding::new("up", SelectPrev, Some("menu")),
|
||||
Binding::new("ctrl-p", SelectPrev, Some("menu")),
|
||||
Binding::new("down", SelectNext, Some("menu")),
|
||||
Binding::new("ctrl-n", SelectNext, Some("menu")),
|
||||
Binding::new("cmd-up", SelectFirst, Some("menu")),
|
||||
Binding::new("cmd-down", SelectLast, Some("menu")),
|
||||
Binding::new("enter", Confirm, Some("menu")),
|
||||
]);
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_actions,
|
||||
keymap::Binding,
|
||||
impl_actions, impl_internal_actions,
|
||||
platform::{CursorStyle, NavigationDirection},
|
||||
AppContext, Entity, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::{ProjectEntryId, ProjectPath};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
|
||||
use util::ResultExt;
|
||||
@ -28,29 +28,33 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Split(pub SplitDirection);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CloseItem(pub CloseItemParams);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ActivateItem(pub usize);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GoBack(pub Option<WeakViewHandle<Pane>>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GoForward(pub Option<WeakViewHandle<Pane>>);
|
||||
|
||||
impl_actions!(pane, [Split, CloseItem, ActivateItem, GoBack, GoForward,]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CloseItemParams {
|
||||
pub struct CloseItem {
|
||||
pub item_id: usize,
|
||||
pub pane: WeakViewHandle<Pane>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct ActivateItem(pub usize);
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct GoBack {
|
||||
#[serde(skip_deserializing)]
|
||||
pub pane: Option<WeakViewHandle<Pane>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct GoForward {
|
||||
#[serde(skip_deserializing)]
|
||||
pub pane: Option<WeakViewHandle<Pane>>,
|
||||
}
|
||||
|
||||
impl_actions!(pane, [Split, GoBack, GoForward]);
|
||||
impl_internal_actions!(pane, [CloseItem, ActivateItem]);
|
||||
|
||||
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
@ -66,8 +70,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_async_action(Pane::close_active_item);
|
||||
cx.add_async_action(Pane::close_inactive_items);
|
||||
cx.add_async_action(|workspace: &mut Workspace, action: &CloseItem, cx| {
|
||||
let pane = action.0.pane.upgrade(cx)?;
|
||||
Some(Pane::close_item(workspace, pane, action.0.item_id, cx))
|
||||
let pane = action.pane.upgrade(cx)?;
|
||||
Some(Pane::close_item(workspace, pane, action.item_id, cx))
|
||||
});
|
||||
cx.add_action(|pane: &mut Pane, action: &Split, cx| {
|
||||
pane.split(action.0, cx);
|
||||
@ -76,7 +80,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
Pane::go_back(
|
||||
workspace,
|
||||
action
|
||||
.0
|
||||
.pane
|
||||
.as_ref()
|
||||
.and_then(|weak_handle| weak_handle.upgrade(cx)),
|
||||
cx,
|
||||
@ -87,26 +91,13 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
Pane::go_forward(
|
||||
workspace,
|
||||
action
|
||||
.0
|
||||
.pane
|
||||
.as_ref()
|
||||
.and_then(|weak_handle| weak_handle.upgrade(cx)),
|
||||
cx,
|
||||
)
|
||||
.detach();
|
||||
});
|
||||
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")),
|
||||
Binding::new("shift-cmd-}", ActivateNextItem, Some("Pane")),
|
||||
Binding::new("cmd-w", CloseActiveItem, Some("Pane")),
|
||||
Binding::new("alt-cmd-w", CloseInactiveItems, Some("Pane")),
|
||||
Binding::new("cmd-k up", Split(SplitDirection::Up), Some("Pane")),
|
||||
Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
|
||||
Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
|
||||
Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
|
||||
Binding::new("ctrl--", GoBack(None), Some("Pane")),
|
||||
Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")),
|
||||
]);
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
@ -747,10 +738,10 @@ impl Pane {
|
||||
.on_click({
|
||||
let pane = pane.clone();
|
||||
move |cx| {
|
||||
cx.dispatch_action(CloseItem(CloseItemParams {
|
||||
cx.dispatch_action(CloseItem {
|
||||
item_id,
|
||||
pane: pane.clone(),
|
||||
}))
|
||||
})
|
||||
}
|
||||
})
|
||||
.named("close-tab-icon")
|
||||
@ -816,8 +807,8 @@ impl View for Pane {
|
||||
.on_navigate_mouse_down(move |direction, cx| {
|
||||
let this = this.clone();
|
||||
match direction {
|
||||
NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
|
||||
NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
|
||||
NavigationDirection::Back => cx.dispatch_action(GoBack { pane: Some(this) }),
|
||||
NavigationDirection::Forward => cx.dispatch_action(GoForward { pane: Some(this) }),
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -4,6 +4,7 @@ use client::PeerId;
|
||||
use collections::HashMap;
|
||||
use gpui::{elements::*, Axis, Border, ViewHandle};
|
||||
use project::Collaborator;
|
||||
use serde::Deserialize;
|
||||
use theme::Theme;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@ -254,7 +255,7 @@ impl PaneAxis {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
pub enum SplitDirection {
|
||||
Up,
|
||||
Down,
|
||||
|
@ -1,5 +1,7 @@
|
||||
use super::Workspace;
|
||||
use gpui::{elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, RenderContext};
|
||||
use gpui::{
|
||||
elements::*, impl_internal_actions, platform::CursorStyle, AnyViewHandle, RenderContext,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use theme::Theme;
|
||||
|
||||
@ -27,7 +29,7 @@ pub struct ToggleSidebarItem(pub SidebarItemId);
|
||||
#[derive(Clone)]
|
||||
pub struct ToggleSidebarItemFocus(pub SidebarItemId);
|
||||
|
||||
impl_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]);
|
||||
impl_internal_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SidebarItemId {
|
||||
|
@ -17,9 +17,8 @@ use gpui::{
|
||||
color::Color,
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
||||
impl_actions,
|
||||
impl_internal_actions,
|
||||
json::{self, to_string_pretty, ToJson},
|
||||
keymap::Binding,
|
||||
platform::{CursorStyle, WindowOptions},
|
||||
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, ClipboardItem, Entity,
|
||||
ImageData, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task,
|
||||
@ -32,7 +31,7 @@ pub use pane_group::*;
|
||||
use postage::prelude::Stream;
|
||||
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree};
|
||||
use settings::Settings;
|
||||
use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
|
||||
use sidebar::{Side, Sidebar, ToggleSidebarItem, ToggleSidebarItemFocus};
|
||||
use status_bar::StatusBar;
|
||||
pub use status_bar::StatusItemView;
|
||||
use std::{
|
||||
@ -101,14 +100,13 @@ pub struct ToggleFollow(pub PeerId);
|
||||
#[derive(Clone)]
|
||||
pub struct JoinProject(pub JoinProjectParams);
|
||||
|
||||
impl_actions!(
|
||||
impl_internal_actions!(
|
||||
workspace,
|
||||
[Open, OpenNew, OpenPaths, ToggleFollow, JoinProject]
|
||||
);
|
||||
|
||||
pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
|
||||
pane::init(cx);
|
||||
menu::init(cx);
|
||||
|
||||
cx.add_global_action(open);
|
||||
cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| {
|
||||
@ -144,29 +142,6 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
|
||||
cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
|
||||
workspace.activate_next_pane(cx)
|
||||
});
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("ctrl-alt-cmd-f", FollowNextCollaborator, None),
|
||||
Binding::new("cmd-s", Save, None),
|
||||
Binding::new("cmd-alt-i", DebugElements, None),
|
||||
Binding::new("cmd-k cmd-left", ActivatePreviousPane, None),
|
||||
Binding::new("cmd-k cmd-right", ActivateNextPane, None),
|
||||
Binding::new(
|
||||
"cmd-shift-!",
|
||||
ToggleSidebarItem(SidebarItemId {
|
||||
side: Side::Left,
|
||||
item_index: 0,
|
||||
}),
|
||||
None,
|
||||
),
|
||||
Binding::new(
|
||||
"cmd-1",
|
||||
ToggleSidebarItemFocus(SidebarItemId {
|
||||
side: Side::Left,
|
||||
item_index: 0,
|
||||
}),
|
||||
None,
|
||||
),
|
||||
]);
|
||||
|
||||
client.add_view_request_handler(Workspace::handle_follow);
|
||||
client.add_view_message_handler(Workspace::handle_unfollow);
|
||||
@ -630,6 +605,7 @@ pub struct WorkspaceParams {
|
||||
pub client: Arc<Client>,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub themes: Arc<ThemeRegistry>,
|
||||
pub user_store: ModelHandle<UserStore>,
|
||||
pub channel_list: ModelHandle<ChannelList>,
|
||||
}
|
||||
@ -659,6 +635,7 @@ impl WorkspaceParams {
|
||||
channel_list: cx
|
||||
.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)),
|
||||
client,
|
||||
themes: ThemeRegistry::new((), cx.font_cache().clone()),
|
||||
fs,
|
||||
languages,
|
||||
user_store,
|
||||
@ -677,6 +654,7 @@ impl WorkspaceParams {
|
||||
),
|
||||
client: app_state.client.clone(),
|
||||
fs: app_state.fs.clone(),
|
||||
themes: app_state.themes.clone(),
|
||||
languages: app_state.languages.clone(),
|
||||
user_store: app_state.user_store.clone(),
|
||||
channel_list: app_state.channel_list.clone(),
|
||||
@ -694,6 +672,7 @@ pub struct Workspace {
|
||||
user_store: ModelHandle<client::UserStore>,
|
||||
remote_entity_subscription: Option<Subscription>,
|
||||
fs: Arc<dyn Fs>,
|
||||
themes: Arc<ThemeRegistry>,
|
||||
modal: Option<AnyViewHandle>,
|
||||
center: PaneGroup,
|
||||
left_sidebar: Sidebar,
|
||||
@ -802,6 +781,7 @@ impl Workspace {
|
||||
remote_entity_subscription: None,
|
||||
user_store: params.user_store.clone(),
|
||||
fs: params.fs.clone(),
|
||||
themes: params.themes.clone(),
|
||||
left_sidebar: Sidebar::new(Side::Left),
|
||||
right_sidebar: Sidebar::new(Side::Right),
|
||||
project: params.project.clone(),
|
||||
@ -834,6 +814,10 @@ impl Workspace {
|
||||
&self.project
|
||||
}
|
||||
|
||||
pub fn themes(&self) -> Arc<ThemeRegistry> {
|
||||
self.themes.clone()
|
||||
}
|
||||
|
||||
pub fn worktrees<'a>(
|
||||
&self,
|
||||
cx: &'a AppContext,
|
||||
|
@ -29,6 +29,7 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
assets = { path = "../assets" }
|
||||
breadcrumbs = { path = "../breadcrumbs" }
|
||||
chat_panel = { path = "../chat_panel" }
|
||||
collections = { path = "../collections" }
|
||||
|
@ -2,6 +2,7 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assets::Assets;
|
||||
use client::{self, http, ChannelList, UserStore};
|
||||
use fs::OpenOptions;
|
||||
use futures::{channel::oneshot, StreamExt};
|
||||
@ -9,19 +10,17 @@ use gpui::{App, AssetSource, Task};
|
||||
use log::LevelFilter;
|
||||
use parking_lot::Mutex;
|
||||
use project::Fs;
|
||||
use settings::{self, Settings};
|
||||
use settings::{self, KeymapFile, Settings, SettingsFileContent};
|
||||
use smol::process::Command;
|
||||
use std::{env, fs, path::PathBuf, sync::Arc};
|
||||
use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
|
||||
use util::ResultExt;
|
||||
use workspace::{self, AppState, OpenNew, OpenPaths};
|
||||
use zed::{
|
||||
self,
|
||||
assets::Assets,
|
||||
build_window_options, build_workspace,
|
||||
self, build_window_options, build_workspace,
|
||||
fs::RealFs,
|
||||
languages, menus,
|
||||
settings_file::{settings_from_files, SettingsFile},
|
||||
settings_file::{settings_from_files, watch_keymap_file, WatchedJsonFile},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@ -62,8 +61,16 @@ fn main() {
|
||||
tab_size: Some(2),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_overrides(
|
||||
"TSX",
|
||||
settings::LanguageOverride {
|
||||
tab_size: Some(2),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let settings_file = load_settings_file(&app, fs.clone());
|
||||
|
||||
let config_files = load_config_files(&app, fs.clone());
|
||||
|
||||
let login_shell_env_loaded = if stdout_is_a_pty() {
|
||||
Task::ready(())
|
||||
@ -112,13 +119,16 @@ fn main() {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let settings_file = cx.background().block(settings_file).unwrap();
|
||||
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
||||
let mut settings_rx = settings_from_files(
|
||||
default_settings,
|
||||
vec![settings_file],
|
||||
themes.clone(),
|
||||
cx.font_cache().clone(),
|
||||
);
|
||||
|
||||
cx.spawn(|cx| watch_keymap_file(keymap_file, cx)).detach();
|
||||
|
||||
let settings = cx.background().block(settings_rx.next()).unwrap();
|
||||
cx.spawn(|mut cx| async move {
|
||||
while let Some(settings) = settings_rx.next().await {
|
||||
@ -145,8 +155,8 @@ fn main() {
|
||||
build_workspace: &build_workspace,
|
||||
});
|
||||
journal::init(app_state.clone(), cx);
|
||||
theme_selector::init(cx);
|
||||
zed::init(&app_state, cx);
|
||||
theme_selector::init(app_state.themes.clone(), cx);
|
||||
|
||||
cx.set_menus(menus::menus(&app_state.clone()));
|
||||
|
||||
@ -254,14 +264,22 @@ fn load_embedded_fonts(app: &App) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn load_settings_file(app: &App, fs: Arc<dyn Fs>) -> oneshot::Receiver<SettingsFile> {
|
||||
fn load_config_files(
|
||||
app: &App,
|
||||
fs: Arc<dyn Fs>,
|
||||
) -> oneshot::Receiver<(
|
||||
WatchedJsonFile<SettingsFileContent>,
|
||||
WatchedJsonFile<KeymapFile>,
|
||||
)> {
|
||||
let executor = app.background();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
executor
|
||||
.clone()
|
||||
.spawn(async move {
|
||||
let file = SettingsFile::new(fs, &executor, zed::SETTINGS_PATH.clone()).await;
|
||||
tx.send(file).ok()
|
||||
let settings_file =
|
||||
WatchedJsonFile::new(fs.clone(), &executor, zed::SETTINGS_PATH.clone()).await;
|
||||
let keymap_file = WatchedJsonFile::new(fs, &executor, zed::KEYMAP_PATH.clone()).await;
|
||||
tx.send((settings_file, keymap_file)).ok()
|
||||
})
|
||||
.detach();
|
||||
rx
|
||||
|
@ -1,17 +1,21 @@
|
||||
use futures::{stream, StreamExt};
|
||||
use gpui::{executor, FontCache};
|
||||
use gpui::{executor, AsyncAppContext, FontCache};
|
||||
use postage::sink::Sink as _;
|
||||
use postage::{prelude::Stream, watch};
|
||||
use project::Fs;
|
||||
use settings::{Settings, SettingsFileContent};
|
||||
use serde::Deserialize;
|
||||
use settings::{KeymapFile, Settings, SettingsFileContent};
|
||||
use std::{path::Path, sync::Arc, time::Duration};
|
||||
use theme::ThemeRegistry;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SettingsFile(watch::Receiver<SettingsFileContent>);
|
||||
pub struct WatchedJsonFile<T>(watch::Receiver<T>);
|
||||
|
||||
impl SettingsFile {
|
||||
impl<T> WatchedJsonFile<T>
|
||||
where
|
||||
T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync,
|
||||
{
|
||||
pub async fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
executor: &executor::Background,
|
||||
@ -35,21 +39,21 @@ impl SettingsFile {
|
||||
Self(rx)
|
||||
}
|
||||
|
||||
async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<SettingsFileContent> {
|
||||
async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<T> {
|
||||
if fs.is_file(&path).await {
|
||||
fs.load(&path)
|
||||
.await
|
||||
.log_err()
|
||||
.and_then(|data| serde_json::from_str(&data).log_err())
|
||||
} else {
|
||||
Some(SettingsFileContent::default())
|
||||
Some(T::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn settings_from_files(
|
||||
defaults: Settings,
|
||||
sources: Vec<SettingsFile>,
|
||||
sources: Vec<WatchedJsonFile<SettingsFileContent>>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
font_cache: Arc<FontCache>,
|
||||
) -> impl futures::stream::Stream<Item = Settings> {
|
||||
@ -72,6 +76,16 @@ pub fn settings_from_files(
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFile>, mut cx: AsyncAppContext) {
|
||||
while let Some(content) = file.0.recv().await {
|
||||
cx.update(|cx| {
|
||||
cx.clear_bindings();
|
||||
settings::KeymapFile::load_defaults(cx);
|
||||
content.add(cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -102,9 +116,9 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let source1 = SettingsFile::new(fs.clone(), &executor, "/settings1.json".as_ref()).await;
|
||||
let source2 = SettingsFile::new(fs.clone(), &executor, "/settings2.json".as_ref()).await;
|
||||
let source3 = SettingsFile::new(fs.clone(), &executor, "/settings3.json".as_ref()).await;
|
||||
let source1 = WatchedJsonFile::new(fs.clone(), &executor, "/settings1.json".as_ref()).await;
|
||||
let source2 = WatchedJsonFile::new(fs.clone(), &executor, "/settings2.json".as_ref()).await;
|
||||
let source3 = WatchedJsonFile::new(fs.clone(), &executor, "/settings3.json".as_ref()).await;
|
||||
|
||||
let mut settings_rx = settings_from_files(
|
||||
cx.read(Settings::test),
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{assets::Assets, build_window_options, build_workspace, AppState};
|
||||
use crate::{build_window_options, build_workspace, AppState};
|
||||
use assets::Assets;
|
||||
use client::{test::FakeHttpClient, ChannelList, Client, UserStore};
|
||||
use gpui::MutableAppContext;
|
||||
use language::LanguageRegistry;
|
||||
|
@ -1,4 +1,3 @@
|
||||
pub mod assets;
|
||||
pub mod languages;
|
||||
pub mod menus;
|
||||
pub mod settings_file;
|
||||
@ -14,8 +13,6 @@ pub use editor;
|
||||
use gpui::{
|
||||
actions,
|
||||
geometry::vector::vec2f,
|
||||
impl_actions,
|
||||
keymap::Binding,
|
||||
platform::{WindowBounds, WindowOptions},
|
||||
ModelHandle, ViewContext,
|
||||
};
|
||||
@ -30,12 +27,16 @@ use std::{path::PathBuf, sync::Arc};
|
||||
pub use workspace;
|
||||
use workspace::{AppState, Workspace, WorkspaceParams};
|
||||
|
||||
actions!(zed, [About, Quit, OpenSettings]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AdjustBufferFontSize(pub f32);
|
||||
|
||||
impl_actions!(zed, [AdjustBufferFontSize]);
|
||||
actions!(
|
||||
zed,
|
||||
[
|
||||
About,
|
||||
Quit,
|
||||
OpenSettings,
|
||||
IncreaseBufferFontSize,
|
||||
DecreaseBufferFontSize
|
||||
]
|
||||
);
|
||||
|
||||
const MIN_FONT_SIZE: f32 = 6.0;
|
||||
|
||||
@ -44,20 +45,23 @@ lazy_static! {
|
||||
.expect("failed to determine home directory")
|
||||
.join(".zed");
|
||||
pub static ref SETTINGS_PATH: PathBuf = ROOT_PATH.join("settings.json");
|
||||
pub static ref KEYMAP_PATH: PathBuf = ROOT_PATH.join("keymap.json");
|
||||
}
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
|
||||
cx.add_global_action(quit);
|
||||
cx.add_global_action({
|
||||
move |action: &AdjustBufferFontSize, cx| {
|
||||
cx.update_global::<Settings, _, _>(|settings, cx| {
|
||||
settings.buffer_font_size =
|
||||
(settings.buffer_font_size + action.0).max(MIN_FONT_SIZE);
|
||||
cx.refresh_windows();
|
||||
});
|
||||
}
|
||||
cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
|
||||
cx.update_global::<Settings, _, _>(|settings, cx| {
|
||||
settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE);
|
||||
cx.refresh_windows();
|
||||
});
|
||||
});
|
||||
cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
|
||||
cx.update_global::<Settings, _, _>(|settings, cx| {
|
||||
settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE);
|
||||
cx.refresh_windows();
|
||||
});
|
||||
});
|
||||
|
||||
cx.add_action({
|
||||
let app_state = app_state.clone();
|
||||
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
||||
@ -99,11 +103,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
|
||||
|
||||
workspace::lsp_status::init(cx);
|
||||
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("cmd-=", AdjustBufferFontSize(1.), None),
|
||||
Binding::new("cmd--", AdjustBufferFontSize(-1.), None),
|
||||
Binding::new("cmd-,", OpenSettings, None),
|
||||
])
|
||||
settings::KeymapFile::load_defaults(cx);
|
||||
}
|
||||
|
||||
pub fn build_workspace(
|
||||
@ -134,19 +134,23 @@ pub fn build_workspace(
|
||||
client: app_state.client.clone(),
|
||||
fs: app_state.fs.clone(),
|
||||
languages: app_state.languages.clone(),
|
||||
themes: app_state.themes.clone(),
|
||||
user_store: app_state.user_store.clone(),
|
||||
channel_list: app_state.channel_list.clone(),
|
||||
};
|
||||
let mut workspace = Workspace::new(&workspace_params, cx);
|
||||
let project = workspace.project().clone();
|
||||
|
||||
let theme_names = app_state.themes.list().collect();
|
||||
let language_names = app_state.languages.language_names();
|
||||
|
||||
project.update(cx, |project, _| {
|
||||
project.set_language_server_settings(serde_json::json!({
|
||||
"json": {
|
||||
"schemas": [
|
||||
{
|
||||
"fileMatch": "**/.zed/settings.json",
|
||||
"schema": Settings::file_json_schema(),
|
||||
"schema": Settings::file_json_schema(theme_names, language_names),
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -202,9 +206,8 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::assets::Assets;
|
||||
|
||||
use super::*;
|
||||
use assets::Assets;
|
||||
use editor::{DisplayPoint, Editor};
|
||||
use gpui::{AssetSource, MutableAppContext, TestAppContext, ViewHandle};
|
||||
use project::{Fs, ProjectPath};
|
||||
|
@ -10,7 +10,7 @@ for (let theme of themes) {
|
||||
let styleTree = snakeCase(app(theme));
|
||||
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
||||
let outPath = path.resolve(
|
||||
`${__dirname}/../../crates/zed/assets/themes/${theme.name}.json`
|
||||
`${__dirname}/../assets/themes/${theme.name}.json`
|
||||
);
|
||||
fs.writeFileSync(outPath, styleTreeJSON);
|
||||
console.log(`- ${outPath} created`);
|
||||
|