commit
5041300b52
27 changed files with 746 additions and 600 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -6104,7 +6104,6 @@ dependencies = [
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"smol",
|
"smol",
|
||||||
"sqlez_macros",
|
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"uuid 1.2.2",
|
"uuid 1.2.2",
|
||||||
]
|
]
|
||||||
|
|
|
@ -315,7 +315,9 @@
|
||||||
{
|
{
|
||||||
"context": "Editor && VimWaiting",
|
"context": "Editor && VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"*": "gpui::KeyPressed"
|
"tab": "vim::Tab",
|
||||||
|
"enter": "vim::Enter",
|
||||||
|
"escape": "editor::Cancel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -65,7 +65,7 @@ impl CommandPalette {
|
||||||
action,
|
action,
|
||||||
keystrokes: bindings
|
keystrokes: bindings
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|binding| binding.keystrokes())
|
.map(|binding| binding.keystrokes())
|
||||||
.last()
|
.last()
|
||||||
.map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
|
.map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
|
||||||
})
|
})
|
||||||
|
|
|
@ -337,7 +337,7 @@ impl DisplaySnapshot {
|
||||||
.map(|h| h.text)
|
.map(|h| h.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns text chunks starting at the end of the given display row in reverse until the start of the file
|
/// Returns text chunks starting at the end of the given display row in reverse until the start of the file
|
||||||
pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
|
pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
|
||||||
(0..=display_row).into_iter().rev().flat_map(|row| {
|
(0..=display_row).into_iter().rev().flat_map(|row| {
|
||||||
self.blocks_snapshot
|
self.blocks_snapshot
|
||||||
|
@ -411,6 +411,67 @@ impl DisplaySnapshot {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of the start positions of the occurances of `target` in the `self` after `from`
|
||||||
|
/// Stops if `condition` returns false for any of the character position pairs observed.
|
||||||
|
pub fn find_while<'a>(
|
||||||
|
&'a self,
|
||||||
|
from: DisplayPoint,
|
||||||
|
target: &str,
|
||||||
|
condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
|
||||||
|
) -> impl Iterator<Item = DisplayPoint> + 'a {
|
||||||
|
Self::find_internal(self.chars_at(from), target.chars().collect(), condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of the end positions of the occurances of `target` in the `self` before `from`
|
||||||
|
/// Stops if `condition` returns false for any of the character position pairs observed.
|
||||||
|
pub fn reverse_find_while<'a>(
|
||||||
|
&'a self,
|
||||||
|
from: DisplayPoint,
|
||||||
|
target: &str,
|
||||||
|
condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
|
||||||
|
) -> impl Iterator<Item = DisplayPoint> + 'a {
|
||||||
|
Self::find_internal(
|
||||||
|
self.reverse_chars_at(from),
|
||||||
|
target.chars().rev().collect(),
|
||||||
|
condition,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_internal<'a>(
|
||||||
|
iterator: impl Iterator<Item = (char, DisplayPoint)> + 'a,
|
||||||
|
target: Vec<char>,
|
||||||
|
mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
|
||||||
|
) -> impl Iterator<Item = DisplayPoint> + 'a {
|
||||||
|
// List of partial matches with the index of the last seen character in target and the starting point of the match
|
||||||
|
let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new();
|
||||||
|
iterator
|
||||||
|
.take_while(move |(ch, point)| condition(*ch, *point))
|
||||||
|
.filter_map(move |(ch, point)| {
|
||||||
|
if Some(&ch) == target.get(0) {
|
||||||
|
partial_matches.push((0, point));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut found = None;
|
||||||
|
// Keep partial matches that have the correct next character
|
||||||
|
partial_matches.retain_mut(|(match_position, match_start)| {
|
||||||
|
if target.get(*match_position) == Some(&ch) {
|
||||||
|
*match_position += 1;
|
||||||
|
if *match_position == target.len() {
|
||||||
|
found = Some(match_start.clone());
|
||||||
|
// This match is completed. No need to keep tracking it
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
found
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
|
pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut column = 0;
|
let mut column = 0;
|
||||||
|
@ -627,7 +688,7 @@ pub mod tests {
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use util::test::{marked_text_ranges, sample_text};
|
use util::test::{marked_text_offsets, marked_text_ranges, sample_text};
|
||||||
use Bias::*;
|
use Bias::*;
|
||||||
|
|
||||||
#[gpui::test(iterations = 100)]
|
#[gpui::test(iterations = 100)]
|
||||||
|
@ -1418,6 +1479,32 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_internal() {
|
||||||
|
assert("This is a ˇtest of find internal", "test");
|
||||||
|
assert("Some text ˇaˇaˇaa with repeated characters", "aa");
|
||||||
|
|
||||||
|
fn assert(marked_text: &str, target: &str) {
|
||||||
|
let (text, expected_offsets) = marked_text_offsets(marked_text);
|
||||||
|
|
||||||
|
let chars = text
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
|
||||||
|
let target = target.chars();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected_offsets
|
||||||
|
.into_iter()
|
||||||
|
.map(|offset| offset as u32)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
|
||||||
|
.map(|point| point.column())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn syntax_chunks<'a>(
|
fn syntax_chunks<'a>(
|
||||||
rows: Range<u32>,
|
rows: Range<u32>,
|
||||||
map: &ModelHandle<DisplayMap>,
|
map: &ModelHandle<DisplayMap>,
|
||||||
|
|
|
@ -400,7 +400,7 @@ pub enum SelectMode {
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum EditorMode {
|
pub enum EditorMode {
|
||||||
SingleLine,
|
SingleLine,
|
||||||
AutoHeight { max_lines: usize },
|
AutoHeight { max_lines: usize },
|
||||||
|
@ -1732,11 +1732,13 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||||
|
let text: Arc<str> = text.into();
|
||||||
|
|
||||||
if !self.input_enabled {
|
if !self.input_enabled {
|
||||||
|
cx.emit(Event::InputIgnored { text });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text: Arc<str> = text.into();
|
|
||||||
let selections = self.selections.all_adjusted(cx);
|
let selections = self.selections.all_adjusted(cx);
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
let mut new_selections = Vec::with_capacity(selections.len());
|
let mut new_selections = Vec::with_capacity(selections.len());
|
||||||
|
@ -6187,6 +6189,9 @@ impl Deref for EditorSnapshot {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
InputIgnored {
|
||||||
|
text: Arc<str>,
|
||||||
|
},
|
||||||
ExcerptsAdded {
|
ExcerptsAdded {
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
predecessor: ExcerptId,
|
predecessor: ExcerptId,
|
||||||
|
@ -6253,8 +6258,10 @@ impl View for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
let focused_event = EditorFocused(cx.handle());
|
if cx.is_self_focused() {
|
||||||
cx.emit_global(focused_event);
|
let focused_event = EditorFocused(cx.handle());
|
||||||
|
cx.emit_global(focused_event);
|
||||||
|
}
|
||||||
if let Some(rename) = self.pending_rename.as_ref() {
|
if let Some(rename) = self.pending_rename.as_ref() {
|
||||||
cx.focus(&rename.editor);
|
cx.focus(&rename.editor);
|
||||||
} else {
|
} else {
|
||||||
|
@ -6393,26 +6400,29 @@ impl View for Editor {
|
||||||
text: &str,
|
text: &str,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
self.transact(cx, |this, cx| {
|
||||||
|
if this.input_enabled {
|
||||||
|
let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
|
||||||
|
let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
|
||||||
|
Some(this.selection_replacement_ranges(range_utf16, cx))
|
||||||
|
} else {
|
||||||
|
this.marked_text_ranges(cx)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new_selected_ranges) = new_selected_ranges {
|
||||||
|
this.change_selections(None, cx, |selections| {
|
||||||
|
selections.select_ranges(new_selected_ranges)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handle_input(text, cx);
|
||||||
|
});
|
||||||
|
|
||||||
if !self.input_enabled {
|
if !self.input_enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.transact(cx, |this, cx| {
|
|
||||||
let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
|
|
||||||
let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
|
|
||||||
Some(this.selection_replacement_ranges(range_utf16, cx))
|
|
||||||
} else {
|
|
||||||
this.marked_text_ranges(cx)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(new_selected_ranges) = new_selected_ranges {
|
|
||||||
this.change_selections(None, cx, |selections| {
|
|
||||||
selections.select_ranges(new_selected_ranges)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.handle_input(text, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(transaction) = self.ime_transaction {
|
if let Some(transaction) = self.ime_transaction {
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.group_until_transaction(transaction, cx);
|
buffer.group_until_transaction(transaction, cx);
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
pub mod action;
|
pub mod action;
|
||||||
mod callback_collection;
|
mod callback_collection;
|
||||||
|
mod menu;
|
||||||
|
pub(crate) mod ref_counts;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod test_app_context;
|
pub mod test_app_context;
|
||||||
|
mod window_input_handler;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, Any, TypeId},
|
any::{type_name, Any, TypeId},
|
||||||
|
@ -19,34 +22,38 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use pathfinder_geometry::vector::Vector2F;
|
use pathfinder_geometry::vector::Vector2F;
|
||||||
use postage::oneshot;
|
use postage::oneshot;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use smol::prelude::*;
|
use smol::prelude::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use action::*;
|
pub use action::*;
|
||||||
use callback_collection::CallbackCollection;
|
use callback_collection::CallbackCollection;
|
||||||
use collections::{hash_map::Entry, HashMap, HashSet, VecDeque};
|
use collections::{hash_map::Entry, HashMap, HashSet, VecDeque};
|
||||||
|
pub use menu::*;
|
||||||
use platform::Event;
|
use platform::Event;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
use ref_counts::LeakDetector;
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use test_app_context::{ContextHandle, TestAppContext};
|
pub use test_app_context::{ContextHandle, TestAppContext};
|
||||||
use uuid::Uuid;
|
use window_input_handler::WindowInputHandler;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
elements::ElementBox,
|
elements::ElementBox,
|
||||||
executor::{self, Task},
|
executor::{self, Task},
|
||||||
geometry::rect::RectF,
|
|
||||||
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
|
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
|
||||||
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
|
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
|
||||||
presenter::Presenter,
|
presenter::Presenter,
|
||||||
util::post_inc,
|
util::post_inc,
|
||||||
Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, KeyUpEvent,
|
Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, KeyUpEvent,
|
||||||
ModifiersChangedEvent, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache,
|
ModifiersChangedEvent, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache,
|
||||||
WindowBounds,
|
WindowBounds,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::ref_counts::RefCounts;
|
||||||
|
|
||||||
pub trait Entity: 'static {
|
pub trait Entity: 'static {
|
||||||
type Event;
|
type Event;
|
||||||
|
|
||||||
|
@ -174,31 +181,12 @@ pub trait UpdateView {
|
||||||
T: View;
|
T: View;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Menu<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub items: Vec<MenuItem<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum MenuItem<'a> {
|
|
||||||
Separator,
|
|
||||||
Submenu(Menu<'a>),
|
|
||||||
Action {
|
|
||||||
name: &'a str,
|
|
||||||
action: Box<dyn Action>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct App(Rc<RefCell<MutableAppContext>>);
|
pub struct App(Rc<RefCell<MutableAppContext>>);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AsyncAppContext(Rc<RefCell<MutableAppContext>>);
|
pub struct AsyncAppContext(Rc<RefCell<MutableAppContext>>);
|
||||||
|
|
||||||
pub struct WindowInputHandler {
|
|
||||||
app: Rc<RefCell<MutableAppContext>>,
|
|
||||||
window_id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(asset_source: impl AssetSource) -> Result<Self> {
|
pub fn new(asset_source: impl AssetSource) -> Result<Self> {
|
||||||
let platform = platform::current::platform();
|
let platform = platform::current::platform();
|
||||||
|
@ -220,33 +208,7 @@ impl App {
|
||||||
cx.borrow_mut().quit();
|
cx.borrow_mut().quit();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
foreground_platform.on_will_open_menu(Box::new({
|
setup_menu_handlers(foreground_platform.as_ref(), &app);
|
||||||
let cx = app.0.clone();
|
|
||||||
move || {
|
|
||||||
let mut cx = cx.borrow_mut();
|
|
||||||
cx.keystroke_matcher.clear_pending();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
foreground_platform.on_validate_menu_command(Box::new({
|
|
||||||
let cx = app.0.clone();
|
|
||||||
move |action| {
|
|
||||||
let cx = cx.borrow_mut();
|
|
||||||
!cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
foreground_platform.on_menu_command(Box::new({
|
|
||||||
let cx = app.0.clone();
|
|
||||||
move |action| {
|
|
||||||
let mut cx = cx.borrow_mut();
|
|
||||||
if let Some(key_window_id) = cx.cx.platform.key_window_id() {
|
|
||||||
if let Some(view_id) = cx.focused_view_id(key_window_id) {
|
|
||||||
cx.handle_dispatch_action_from_effect(key_window_id, Some(view_id), action);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.dispatch_global_action_any(action);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
|
app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
|
||||||
Ok(app)
|
Ok(app)
|
||||||
|
@ -349,94 +311,6 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowInputHandler {
|
|
||||||
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
|
|
||||||
where
|
|
||||||
F: FnOnce(&dyn AnyView, &AppContext) -> T,
|
|
||||||
{
|
|
||||||
// Input-related application hooks are sometimes called by the OS during
|
|
||||||
// a call to a window-manipulation API, like prompting the user for file
|
|
||||||
// paths. In that case, the AppContext will already be borrowed, so any
|
|
||||||
// InputHandler methods need to fail gracefully.
|
|
||||||
//
|
|
||||||
// See https://github.com/zed-industries/community/issues/444
|
|
||||||
let app = self.app.try_borrow().ok()?;
|
|
||||||
|
|
||||||
let view_id = app.focused_view_id(self.window_id)?;
|
|
||||||
let view = app.cx.views.get(&(self.window_id, view_id))?;
|
|
||||||
let result = f(view.as_ref(), &app);
|
|
||||||
Some(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
|
|
||||||
where
|
|
||||||
F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T,
|
|
||||||
{
|
|
||||||
let mut app = self.app.try_borrow_mut().ok()?;
|
|
||||||
app.update(|app| {
|
|
||||||
let view_id = app.focused_view_id(self.window_id)?;
|
|
||||||
let mut view = app.cx.views.remove(&(self.window_id, view_id))?;
|
|
||||||
let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
|
|
||||||
app.cx.views.insert((self.window_id, view_id), view);
|
|
||||||
Some(result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputHandler for WindowInputHandler {
|
|
||||||
fn text_for_range(&self, range: Range<usize>) -> Option<String> {
|
|
||||||
self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_text_range(&self) -> Option<Range<usize>> {
|
|
||||||
self.read_focused_view(|view, cx| view.selected_text_range(cx))
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
|
|
||||||
self.update_focused_view(|window_id, view_id, view, cx| {
|
|
||||||
view.replace_text_in_range(range, text, cx, window_id, view_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn marked_text_range(&self) -> Option<Range<usize>> {
|
|
||||||
self.read_focused_view(|view, cx| view.marked_text_range(cx))
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unmark_text(&mut self) {
|
|
||||||
self.update_focused_view(|window_id, view_id, view, cx| {
|
|
||||||
view.unmark_text(cx, window_id, view_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_and_mark_text_in_range(
|
|
||||||
&mut self,
|
|
||||||
range: Option<Range<usize>>,
|
|
||||||
new_text: &str,
|
|
||||||
new_selected_range: Option<Range<usize>>,
|
|
||||||
) {
|
|
||||||
self.update_focused_view(|window_id, view_id, view, cx| {
|
|
||||||
view.replace_and_mark_text_in_range(
|
|
||||||
range,
|
|
||||||
new_text,
|
|
||||||
new_selected_range,
|
|
||||||
cx,
|
|
||||||
window_id,
|
|
||||||
view_id,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
|
||||||
let app = self.app.borrow();
|
|
||||||
let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
|
|
||||||
let presenter = presenter.borrow();
|
|
||||||
presenter.rect_for_text_range(range_utf16, &app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncAppContext {
|
impl AsyncAppContext {
|
||||||
pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
|
pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
|
||||||
where
|
where
|
||||||
|
@ -984,11 +858,6 @@ impl MutableAppContext {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_menus(&mut self, menus: Vec<Menu>) {
|
|
||||||
self.foreground_platform
|
|
||||||
.set_menus(menus, &self.keystroke_matcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_character_palette(&self, window_id: usize) {
|
fn show_character_palette(&self, window_id: usize) {
|
||||||
let (_, window) = &self.presenters_and_platform_windows[&window_id];
|
let (_, window) = &self.presenters_and_platform_windows[&window_id];
|
||||||
window.show_character_palette();
|
window.show_character_palette();
|
||||||
|
@ -1350,7 +1219,7 @@ impl MutableAppContext {
|
||||||
.bindings_for_action_type(action.as_any().type_id())
|
.bindings_for_action_type(action.as_any().type_id())
|
||||||
.find_map(|b| {
|
.find_map(|b| {
|
||||||
if b.match_context(&contexts) {
|
if b.match_context(&contexts) {
|
||||||
b.keystrokes().map(|s| s.into())
|
Some(b.keystrokes().into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -4025,7 +3894,7 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_keystroke<F>(&mut self, mut callback: F) -> Subscription
|
pub fn observe_keystrokes<F>(&mut self, mut callback: F) -> Subscription
|
||||||
where
|
where
|
||||||
F: 'static
|
F: 'static
|
||||||
+ FnMut(
|
+ FnMut(
|
||||||
|
@ -5280,205 +5149,6 @@ impl Subscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref LEAK_BACKTRACE: bool =
|
|
||||||
std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct LeakDetector {
|
|
||||||
next_handle_id: usize,
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
handle_backtraces: HashMap<
|
|
||||||
usize,
|
|
||||||
(
|
|
||||||
Option<&'static str>,
|
|
||||||
HashMap<usize, Option<backtrace::Backtrace>>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
impl LeakDetector {
|
|
||||||
fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
|
|
||||||
let handle_id = post_inc(&mut self.next_handle_id);
|
|
||||||
let entry = self.handle_backtraces.entry(entity_id).or_default();
|
|
||||||
let backtrace = if *LEAK_BACKTRACE {
|
|
||||||
Some(backtrace::Backtrace::new_unresolved())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(type_name) = type_name {
|
|
||||||
entry.0.get_or_insert(type_name);
|
|
||||||
}
|
|
||||||
entry.1.insert(handle_id, backtrace);
|
|
||||||
handle_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
|
|
||||||
if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
|
||||||
assert!(backtraces.remove(&handle_id).is_some());
|
|
||||||
if backtraces.is_empty() {
|
|
||||||
self.handle_backtraces.remove(&entity_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assert_dropped(&mut self, entity_id: usize) {
|
|
||||||
if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
|
||||||
for trace in backtraces.values_mut().flatten() {
|
|
||||||
trace.resolve();
|
|
||||||
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
|
||||||
}
|
|
||||||
|
|
||||||
let hint = if *LEAK_BACKTRACE {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
" – set LEAK_BACKTRACE=1 for more information"
|
|
||||||
};
|
|
||||||
|
|
||||||
panic!(
|
|
||||||
"{} handles to {} {} still exist{}",
|
|
||||||
backtraces.len(),
|
|
||||||
type_name.unwrap_or("entity"),
|
|
||||||
entity_id,
|
|
||||||
hint
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn detect(&mut self) {
|
|
||||||
let mut found_leaks = false;
|
|
||||||
for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
|
|
||||||
eprintln!(
|
|
||||||
"leaked {} handles to {} {}",
|
|
||||||
backtraces.len(),
|
|
||||||
type_name.unwrap_or("entity"),
|
|
||||||
id
|
|
||||||
);
|
|
||||||
for trace in backtraces.values_mut().flatten() {
|
|
||||||
trace.resolve();
|
|
||||||
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
|
||||||
}
|
|
||||||
found_leaks = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hint = if *LEAK_BACKTRACE {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
" – set LEAK_BACKTRACE=1 for more information"
|
|
||||||
};
|
|
||||||
assert!(!found_leaks, "detected leaked handles{}", hint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct RefCounts {
|
|
||||||
entity_counts: HashMap<usize, usize>,
|
|
||||||
element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
|
|
||||||
dropped_models: HashSet<usize>,
|
|
||||||
dropped_views: HashSet<(usize, usize)>,
|
|
||||||
dropped_element_states: HashSet<ElementStateId>,
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
leak_detector: Arc<Mutex<LeakDetector>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ElementStateRefCount {
|
|
||||||
ref_count: usize,
|
|
||||||
frame_id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RefCounts {
|
|
||||||
fn inc_model(&mut self, model_id: usize) {
|
|
||||||
match self.entity_counts.entry(model_id) {
|
|
||||||
Entry::Occupied(mut entry) => {
|
|
||||||
*entry.get_mut() += 1;
|
|
||||||
}
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(1);
|
|
||||||
self.dropped_models.remove(&model_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inc_view(&mut self, window_id: usize, view_id: usize) {
|
|
||||||
match self.entity_counts.entry(view_id) {
|
|
||||||
Entry::Occupied(mut entry) => *entry.get_mut() += 1,
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(1);
|
|
||||||
self.dropped_views.remove(&(window_id, view_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
|
|
||||||
match self.element_state_counts.entry(id) {
|
|
||||||
Entry::Occupied(mut entry) => {
|
|
||||||
let entry = entry.get_mut();
|
|
||||||
if entry.frame_id == frame_id || entry.ref_count >= 2 {
|
|
||||||
panic!("used the same element state more than once in the same frame");
|
|
||||||
}
|
|
||||||
entry.ref_count += 1;
|
|
||||||
entry.frame_id = frame_id;
|
|
||||||
}
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(ElementStateRefCount {
|
|
||||||
ref_count: 1,
|
|
||||||
frame_id,
|
|
||||||
});
|
|
||||||
self.dropped_element_states.remove(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dec_model(&mut self, model_id: usize) {
|
|
||||||
let count = self.entity_counts.get_mut(&model_id).unwrap();
|
|
||||||
*count -= 1;
|
|
||||||
if *count == 0 {
|
|
||||||
self.entity_counts.remove(&model_id);
|
|
||||||
self.dropped_models.insert(model_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dec_view(&mut self, window_id: usize, view_id: usize) {
|
|
||||||
let count = self.entity_counts.get_mut(&view_id).unwrap();
|
|
||||||
*count -= 1;
|
|
||||||
if *count == 0 {
|
|
||||||
self.entity_counts.remove(&view_id);
|
|
||||||
self.dropped_views.insert((window_id, view_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dec_element_state(&mut self, id: ElementStateId) {
|
|
||||||
let entry = self.element_state_counts.get_mut(&id).unwrap();
|
|
||||||
entry.ref_count -= 1;
|
|
||||||
if entry.ref_count == 0 {
|
|
||||||
self.element_state_counts.remove(&id);
|
|
||||||
self.dropped_element_states.insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_entity_alive(&self, entity_id: usize) -> bool {
|
|
||||||
self.entity_counts.contains_key(&entity_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_dropped(
|
|
||||||
&mut self,
|
|
||||||
) -> (
|
|
||||||
HashSet<usize>,
|
|
||||||
HashSet<(usize, usize)>,
|
|
||||||
HashSet<ElementStateId>,
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
std::mem::take(&mut self.dropped_models),
|
|
||||||
std::mem::take(&mut self.dropped_views),
|
|
||||||
std::mem::take(&mut self.dropped_element_states),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
52
crates/gpui/src/app/menu.rs
Normal file
52
crates/gpui/src/app/menu.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::{Action, App, ForegroundPlatform, MutableAppContext};
|
||||||
|
|
||||||
|
pub struct Menu<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub items: Vec<MenuItem<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum MenuItem<'a> {
|
||||||
|
Separator,
|
||||||
|
Submenu(Menu<'a>),
|
||||||
|
Action {
|
||||||
|
name: &'a str,
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MutableAppContext {
|
||||||
|
pub fn set_menus(&mut self, menus: Vec<Menu>) {
|
||||||
|
self.foreground_platform
|
||||||
|
.set_menus(menus, &self.keystroke_matcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform, app: &App) {
|
||||||
|
foreground_platform.on_will_open_menu(Box::new({
|
||||||
|
let cx = app.0.clone();
|
||||||
|
move || {
|
||||||
|
let mut cx = cx.borrow_mut();
|
||||||
|
cx.keystroke_matcher.clear_pending();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
foreground_platform.on_validate_menu_command(Box::new({
|
||||||
|
let cx = app.0.clone();
|
||||||
|
move |action| {
|
||||||
|
let cx = cx.borrow_mut();
|
||||||
|
!cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
foreground_platform.on_menu_command(Box::new({
|
||||||
|
let cx = app.0.clone();
|
||||||
|
move |action| {
|
||||||
|
let mut cx = cx.borrow_mut();
|
||||||
|
if let Some(key_window_id) = cx.cx.platform.key_window_id() {
|
||||||
|
if let Some(view_id) = cx.focused_view_id(key_window_id) {
|
||||||
|
cx.handle_dispatch_action_from_effect(key_window_id, Some(view_id), action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx.dispatch_global_action_any(action);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
217
crates/gpui/src/app/ref_counts.rs
Normal file
217
crates/gpui/src/app/ref_counts.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
use collections::{hash_map::Entry, HashMap, HashSet};
|
||||||
|
|
||||||
|
use crate::{util::post_inc, ElementStateId};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LEAK_BACKTRACE: bool =
|
||||||
|
std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ElementStateRefCount {
|
||||||
|
ref_count: usize,
|
||||||
|
frame_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RefCounts {
|
||||||
|
entity_counts: HashMap<usize, usize>,
|
||||||
|
element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
|
||||||
|
dropped_models: HashSet<usize>,
|
||||||
|
dropped_views: HashSet<(usize, usize)>,
|
||||||
|
dropped_element_states: HashSet<ElementStateId>,
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub leak_detector: Arc<Mutex<LeakDetector>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RefCounts {
|
||||||
|
pub fn new(
|
||||||
|
#[cfg(any(test, feature = "test-support"))] leak_detector: Arc<Mutex<LeakDetector>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
leak_detector,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_model(&mut self, model_id: usize) {
|
||||||
|
match self.entity_counts.entry(model_id) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
*entry.get_mut() += 1;
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(1);
|
||||||
|
self.dropped_models.remove(&model_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_view(&mut self, window_id: usize, view_id: usize) {
|
||||||
|
match self.entity_counts.entry(view_id) {
|
||||||
|
Entry::Occupied(mut entry) => *entry.get_mut() += 1,
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(1);
|
||||||
|
self.dropped_views.remove(&(window_id, view_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
|
||||||
|
match self.element_state_counts.entry(id) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let entry = entry.get_mut();
|
||||||
|
if entry.frame_id == frame_id || entry.ref_count >= 2 {
|
||||||
|
panic!("used the same element state more than once in the same frame");
|
||||||
|
}
|
||||||
|
entry.ref_count += 1;
|
||||||
|
entry.frame_id = frame_id;
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(ElementStateRefCount {
|
||||||
|
ref_count: 1,
|
||||||
|
frame_id,
|
||||||
|
});
|
||||||
|
self.dropped_element_states.remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_model(&mut self, model_id: usize) {
|
||||||
|
let count = self.entity_counts.get_mut(&model_id).unwrap();
|
||||||
|
*count -= 1;
|
||||||
|
if *count == 0 {
|
||||||
|
self.entity_counts.remove(&model_id);
|
||||||
|
self.dropped_models.insert(model_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_view(&mut self, window_id: usize, view_id: usize) {
|
||||||
|
let count = self.entity_counts.get_mut(&view_id).unwrap();
|
||||||
|
*count -= 1;
|
||||||
|
if *count == 0 {
|
||||||
|
self.entity_counts.remove(&view_id);
|
||||||
|
self.dropped_views.insert((window_id, view_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_element_state(&mut self, id: ElementStateId) {
|
||||||
|
let entry = self.element_state_counts.get_mut(&id).unwrap();
|
||||||
|
entry.ref_count -= 1;
|
||||||
|
if entry.ref_count == 0 {
|
||||||
|
self.element_state_counts.remove(&id);
|
||||||
|
self.dropped_element_states.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_entity_alive(&self, entity_id: usize) -> bool {
|
||||||
|
self.entity_counts.contains_key(&entity_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_dropped(
|
||||||
|
&mut self,
|
||||||
|
) -> (
|
||||||
|
HashSet<usize>,
|
||||||
|
HashSet<(usize, usize)>,
|
||||||
|
HashSet<ElementStateId>,
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
std::mem::take(&mut self.dropped_models),
|
||||||
|
std::mem::take(&mut self.dropped_views),
|
||||||
|
std::mem::take(&mut self.dropped_element_states),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LeakDetector {
|
||||||
|
next_handle_id: usize,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
handle_backtraces: HashMap<
|
||||||
|
usize,
|
||||||
|
(
|
||||||
|
Option<&'static str>,
|
||||||
|
HashMap<usize, Option<backtrace::Backtrace>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
impl LeakDetector {
|
||||||
|
pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
|
||||||
|
let handle_id = post_inc(&mut self.next_handle_id);
|
||||||
|
let entry = self.handle_backtraces.entry(entity_id).or_default();
|
||||||
|
let backtrace = if *LEAK_BACKTRACE {
|
||||||
|
Some(backtrace::Backtrace::new_unresolved())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(type_name) = type_name {
|
||||||
|
entry.0.get_or_insert(type_name);
|
||||||
|
}
|
||||||
|
entry.1.insert(handle_id, backtrace);
|
||||||
|
handle_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
|
||||||
|
if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
||||||
|
assert!(backtraces.remove(&handle_id).is_some());
|
||||||
|
if backtraces.is_empty() {
|
||||||
|
self.handle_backtraces.remove(&entity_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_dropped(&mut self, entity_id: usize) {
|
||||||
|
if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
||||||
|
for trace in backtraces.values_mut().flatten() {
|
||||||
|
trace.resolve();
|
||||||
|
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hint = if *LEAK_BACKTRACE {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" – set LEAK_BACKTRACE=1 for more information"
|
||||||
|
};
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"{} handles to {} {} still exist{}",
|
||||||
|
backtraces.len(),
|
||||||
|
type_name.unwrap_or("entity"),
|
||||||
|
entity_id,
|
||||||
|
hint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detect(&mut self) {
|
||||||
|
let mut found_leaks = false;
|
||||||
|
for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
|
||||||
|
eprintln!(
|
||||||
|
"leaked {} handles to {} {}",
|
||||||
|
backtraces.len(),
|
||||||
|
type_name.unwrap_or("entity"),
|
||||||
|
id
|
||||||
|
);
|
||||||
|
for trace in backtraces.values_mut().flatten() {
|
||||||
|
trace.resolve();
|
||||||
|
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
||||||
|
}
|
||||||
|
found_leaks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hint = if *LEAK_BACKTRACE {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" – set LEAK_BACKTRACE=1 for more information"
|
||||||
|
};
|
||||||
|
assert!(!found_leaks, "detected leaked handles{}", hint);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,13 +19,14 @@ use smol::stream::StreamExt;
|
||||||
use crate::{
|
use crate::{
|
||||||
executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
|
executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
|
||||||
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent,
|
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent,
|
||||||
LeakDetector, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith,
|
ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith,
|
||||||
ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle,
|
RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle,
|
||||||
WeakHandle, WindowInputHandler,
|
|
||||||
};
|
};
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
|
|
||||||
use super::{AsyncAppContext, RefCounts};
|
use super::{
|
||||||
|
ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct TestAppContext {
|
pub struct TestAppContext {
|
||||||
cx: Rc<RefCell<MutableAppContext>>,
|
cx: Rc<RefCell<MutableAppContext>>,
|
||||||
|
@ -52,11 +53,7 @@ impl TestAppContext {
|
||||||
platform,
|
platform,
|
||||||
foreground_platform.clone(),
|
foreground_platform.clone(),
|
||||||
font_cache,
|
font_cache,
|
||||||
RefCounts {
|
RefCounts::new(leak_detector),
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
leak_detector,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
cx.next_entity_id = first_entity_id;
|
cx.next_entity_id = first_entity_id;
|
||||||
|
|
98
crates/gpui/src/app/window_input_handler.rs
Normal file
98
crates/gpui/src/app/window_input_handler.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||||
|
|
||||||
|
use pathfinder_geometry::rect::RectF;
|
||||||
|
|
||||||
|
use crate::{AnyView, AppContext, InputHandler, MutableAppContext};
|
||||||
|
|
||||||
|
pub struct WindowInputHandler {
|
||||||
|
pub app: Rc<RefCell<MutableAppContext>>,
|
||||||
|
pub window_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowInputHandler {
|
||||||
|
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&dyn AnyView, &AppContext) -> T,
|
||||||
|
{
|
||||||
|
// Input-related application hooks are sometimes called by the OS during
|
||||||
|
// a call to a window-manipulation API, like prompting the user for file
|
||||||
|
// paths. In that case, the AppContext will already be borrowed, so any
|
||||||
|
// InputHandler methods need to fail gracefully.
|
||||||
|
//
|
||||||
|
// See https://github.com/zed-industries/community/issues/444
|
||||||
|
let app = self.app.try_borrow().ok()?;
|
||||||
|
|
||||||
|
let view_id = app.focused_view_id(self.window_id)?;
|
||||||
|
let view = app.cx.views.get(&(self.window_id, view_id))?;
|
||||||
|
let result = f(view.as_ref(), &app);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T,
|
||||||
|
{
|
||||||
|
let mut app = self.app.try_borrow_mut().ok()?;
|
||||||
|
app.update(|app| {
|
||||||
|
let view_id = app.focused_view_id(self.window_id)?;
|
||||||
|
let mut view = app.cx.views.remove(&(self.window_id, view_id))?;
|
||||||
|
let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
|
||||||
|
app.cx.views.insert((self.window_id, view_id), view);
|
||||||
|
Some(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputHandler for WindowInputHandler {
|
||||||
|
fn text_for_range(&self, range: Range<usize>) -> Option<String> {
|
||||||
|
self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_text_range(&self) -> Option<Range<usize>> {
|
||||||
|
self.read_focused_view(|view, cx| view.selected_text_range(cx))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
|
||||||
|
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||||
|
view.replace_text_in_range(range, text, cx, window_id, view_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marked_text_range(&self) -> Option<Range<usize>> {
|
||||||
|
self.read_focused_view(|view, cx| view.marked_text_range(cx))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmark_text(&mut self) {
|
||||||
|
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||||
|
view.unmark_text(cx, window_id, view_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range: Option<Range<usize>>,
|
||||||
|
new_text: &str,
|
||||||
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
) {
|
||||||
|
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||||
|
view.replace_and_mark_text_in_range(
|
||||||
|
range,
|
||||||
|
new_text,
|
||||||
|
new_selected_range,
|
||||||
|
cx,
|
||||||
|
window_id,
|
||||||
|
view_id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
||||||
|
let app = self.app.borrow();
|
||||||
|
let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
|
||||||
|
let presenter = presenter.borrow();
|
||||||
|
presenter.rect_for_text_range(range_utf16, &app)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,24 +6,15 @@ mod keystroke;
|
||||||
use std::{any::TypeId, fmt::Debug};
|
use std::{any::TypeId, fmt::Debug};
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use serde::Deserialize;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{impl_actions, Action};
|
use crate::Action;
|
||||||
|
|
||||||
pub use binding::{Binding, BindingMatchResult};
|
pub use binding::{Binding, BindingMatchResult};
|
||||||
pub use keymap::Keymap;
|
pub use keymap::Keymap;
|
||||||
pub use keymap_context::{KeymapContext, KeymapContextPredicate};
|
pub use keymap_context::{KeymapContext, KeymapContextPredicate};
|
||||||
pub use keystroke::Keystroke;
|
pub use keystroke::Keystroke;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
|
|
||||||
pub struct KeyPressed {
|
|
||||||
#[serde(default)]
|
|
||||||
pub keystroke: Keystroke,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_actions!(gpui, [KeyPressed]);
|
|
||||||
|
|
||||||
pub struct KeymapMatcher {
|
pub struct KeymapMatcher {
|
||||||
pub contexts: Vec<KeymapContext>,
|
pub contexts: Vec<KeymapContext>,
|
||||||
pending_views: HashMap<usize, KeymapContext>,
|
pending_views: HashMap<usize, KeymapContext>,
|
||||||
|
@ -102,13 +93,7 @@ impl KeymapMatcher {
|
||||||
for binding in self.keymap.bindings().iter().rev() {
|
for binding in self.keymap.bindings().iter().rev() {
|
||||||
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
|
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
|
||||||
{
|
{
|
||||||
BindingMatchResult::Complete(mut action) => {
|
BindingMatchResult::Complete(action) => {
|
||||||
// Swap in keystroke for special KeyPressed action
|
|
||||||
if action.name() == "KeyPressed" && action.namespace() == "gpui" {
|
|
||||||
action = Box::new(KeyPressed {
|
|
||||||
keystroke: keystroke.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
matched_bindings.push((view_id, action))
|
matched_bindings.push((view_id, action))
|
||||||
}
|
}
|
||||||
BindingMatchResult::Partial => {
|
BindingMatchResult::Partial => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke};
|
||||||
|
|
||||||
pub struct Binding {
|
pub struct Binding {
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
keystrokes: Option<SmallVec<[Keystroke; 2]>>,
|
keystrokes: SmallVec<[Keystroke; 2]>,
|
||||||
context_predicate: Option<KeymapContextPredicate>,
|
context_predicate: Option<KeymapContextPredicate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,16 +23,10 @@ impl Binding {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let keystrokes = if keystrokes == "*" {
|
let keystrokes = keystrokes
|
||||||
None // Catch all context
|
.split_whitespace()
|
||||||
} else {
|
.map(Keystroke::parse)
|
||||||
Some(
|
.collect::<Result<_>>()?;
|
||||||
keystrokes
|
|
||||||
.split_whitespace()
|
|
||||||
.map(Keystroke::parse)
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
keystrokes,
|
keystrokes,
|
||||||
|
@ -53,20 +47,10 @@ impl Binding {
|
||||||
pending_keystrokes: &Vec<Keystroke>,
|
pending_keystrokes: &Vec<Keystroke>,
|
||||||
contexts: &[KeymapContext],
|
contexts: &[KeymapContext],
|
||||||
) -> BindingMatchResult {
|
) -> BindingMatchResult {
|
||||||
if self
|
if self.keystrokes.as_ref().starts_with(&pending_keystrokes) && self.match_context(contexts)
|
||||||
.keystrokes
|
|
||||||
.as_ref()
|
|
||||||
.map(|keystrokes| keystrokes.starts_with(&pending_keystrokes))
|
|
||||||
.unwrap_or(true)
|
|
||||||
&& self.match_context(contexts)
|
|
||||||
{
|
{
|
||||||
// If the binding is completed, push it onto the matches list
|
// If the binding is completed, push it onto the matches list
|
||||||
if self
|
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
|
||||||
.keystrokes
|
|
||||||
.as_ref()
|
|
||||||
.map(|keystrokes| keystrokes.len() == pending_keystrokes.len())
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
BindingMatchResult::Complete(self.action.boxed_clone())
|
BindingMatchResult::Complete(self.action.boxed_clone())
|
||||||
} else {
|
} else {
|
||||||
BindingMatchResult::Partial
|
BindingMatchResult::Partial
|
||||||
|
@ -82,14 +66,14 @@ impl Binding {
|
||||||
contexts: &[KeymapContext],
|
contexts: &[KeymapContext],
|
||||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||||
if self.action.eq(action) && self.match_context(contexts) {
|
if self.action.eq(action) && self.match_context(contexts) {
|
||||||
self.keystrokes.clone()
|
Some(self.keystrokes.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keystrokes(&self) -> Option<&[Keystroke]> {
|
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||||
self.keystrokes.as_deref()
|
self.keystrokes.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(&self) -> &dyn Action {
|
pub fn action(&self) -> &dyn Action {
|
||||||
|
|
|
@ -184,7 +184,7 @@ impl MacForegroundPlatform {
|
||||||
.map(|binding| binding.keystrokes());
|
.map(|binding| binding.keystrokes());
|
||||||
|
|
||||||
let item;
|
let item;
|
||||||
if let Some(keystrokes) = keystrokes.flatten() {
|
if let Some(keystrokes) = keystrokes {
|
||||||
if keystrokes.len() == 1 {
|
if keystrokes.len() == 1 {
|
||||||
let keystroke = &keystrokes[0];
|
let keystroke = &keystrokes[0];
|
||||||
let mut mask = NSEventModifierFlags::empty();
|
let mut mask = NSEventModifierFlags::empty();
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
keymap_matcher::KeymapMatcher,
|
keymap_matcher::KeymapMatcher,
|
||||||
Action, ClipboardItem,
|
Action, ClipboardItem, Menu,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
|
@ -77,7 +77,7 @@ impl super::ForegroundPlatform for ForegroundPlatform {
|
||||||
fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
|
fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
|
||||||
fn on_validate_menu_command(&self, _: Box<dyn FnMut(&dyn Action) -> bool>) {}
|
fn on_validate_menu_command(&self, _: Box<dyn FnMut(&dyn Action) -> bool>) {}
|
||||||
fn on_will_open_menu(&self, _: Box<dyn FnMut()>) {}
|
fn on_will_open_menu(&self, _: Box<dyn FnMut()>) {}
|
||||||
fn set_menus(&self, _: Vec<crate::Menu>, _: &KeymapMatcher) {}
|
fn set_menus(&self, _: Vec<Menu>, _: &KeymapMatcher) {}
|
||||||
|
|
||||||
fn prompt_for_paths(
|
fn prompt_for_paths(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -1,14 +1,3 @@
|
||||||
use crate::{
|
|
||||||
elements::Empty,
|
|
||||||
executor::{self, ExecutorEvent},
|
|
||||||
platform,
|
|
||||||
util::CwdBacktrace,
|
|
||||||
Element, ElementBox, Entity, FontCache, Handle, LeakDetector, MutableAppContext, Platform,
|
|
||||||
RenderContext, Subscription, TestAppContext, View,
|
|
||||||
};
|
|
||||||
use futures::StreamExt;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use smol::channel;
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
panic::{self, RefUnwindSafe},
|
panic::{self, RefUnwindSafe},
|
||||||
|
@ -19,6 +8,20 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use smol::channel;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::ref_counts::LeakDetector,
|
||||||
|
elements::Empty,
|
||||||
|
executor::{self, ExecutorEvent},
|
||||||
|
platform,
|
||||||
|
util::CwdBacktrace,
|
||||||
|
Element, ElementBox, Entity, FontCache, Handle, MutableAppContext, Platform, RenderContext,
|
||||||
|
Subscription, TestAppContext, View,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
|
|
21
crates/pando/Cargo.toml
Normal file
21
crates/pando/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "pando"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/pando.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.38"
|
||||||
|
client = { path = "../client" }
|
||||||
|
gpui = { path = "../gpui" }
|
||||||
|
settings = { path = "../settings" }
|
||||||
|
theme = { path = "../theme" }
|
||||||
|
workspace = { path = "../workspace" }
|
||||||
|
sqlez = { path = "../sqlez" }
|
||||||
|
sqlez_macros = { path = "../sqlez_macros" }
|
0
crates/pando/src/file_format.rs
Normal file
0
crates/pando/src/file_format.rs
Normal file
15
crates/pando/src/pando.rs
Normal file
15
crates/pando/src/pando.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//! ## Goals
|
||||||
|
//! - Opinionated Subset of Obsidian. Only the things that cant be done other ways in zed
|
||||||
|
//! - Checked in .zp file is an sqlite db containing graph metadata
|
||||||
|
//! - All nodes are file urls
|
||||||
|
//! - Markdown links auto add soft linked nodes to the db
|
||||||
|
//! - Links create positioning data regardless of if theres a file
|
||||||
|
//! - Lock links to make structure that doesn't rotate or spread
|
||||||
|
//! - Drag from file finder to pando item to add it in
|
||||||
|
//! - For linked files, zoom out to see closest linking pando file
|
||||||
|
|
||||||
|
//! ## Plan
|
||||||
|
//! - [ ] Make item backed by .zp sqlite file with camera position by user account
|
||||||
|
//! - [ ] Render grid of dots and allow scrolling around the grid
|
||||||
|
//! - [ ] Add scale property to layer canvas and manipulate it with pinch zooming
|
||||||
|
//! - [ ] Allow dropping files onto .zp pane. Their relative path is recorded into the file along with
|
|
@ -15,7 +15,4 @@ thread_local = "1.1.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
sqlez_macros = { path = "../sqlez_macros"}
|
|
|
@ -83,7 +83,6 @@ impl Connection {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use sqlez_macros::sql;
|
|
||||||
|
|
||||||
use crate::connection::Connection;
|
use crate::connection::Connection;
|
||||||
|
|
||||||
|
@ -288,21 +287,18 @@ mod test {
|
||||||
let connection = Connection::open_memory(Some("test_create_alter_drop"));
|
let connection = Connection::open_memory(Some("test_create_alter_drop"));
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.migrate(
|
.migrate("first_migration", &["CREATE TABLE table1(a TEXT) STRICT;"])
|
||||||
"first_migration",
|
|
||||||
&[sql!( CREATE TABLE table1(a TEXT) STRICT; )],
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.exec(sql!( INSERT INTO table1(a) VALUES ("test text"); ))
|
.exec("INSERT INTO table1(a) VALUES (\"test text\");")
|
||||||
.unwrap()()
|
.unwrap()()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.migrate(
|
.migrate(
|
||||||
"second_migration",
|
"second_migration",
|
||||||
&[sql!(
|
&[indoc! {"
|
||||||
CREATE TABLE table2(b TEXT) STRICT;
|
CREATE TABLE table2(b TEXT) STRICT;
|
||||||
|
|
||||||
INSERT INTO table2 (b)
|
INSERT INTO table2 (b)
|
||||||
|
@ -311,16 +307,11 @@ mod test {
|
||||||
DROP TABLE table1;
|
DROP TABLE table1;
|
||||||
|
|
||||||
ALTER TABLE table2 RENAME TO table1;
|
ALTER TABLE table2 RENAME TO table1;
|
||||||
)],
|
"}],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let res = &connection
|
let res = &connection.select::<String>("SELECT b FROM table1").unwrap()().unwrap()[0];
|
||||||
.select::<String>(sql!(
|
|
||||||
SELECT b FROM table1
|
|
||||||
))
|
|
||||||
.unwrap()()
|
|
||||||
.unwrap()[0];
|
|
||||||
|
|
||||||
assert_eq!(res, "test text");
|
assert_eq!(res, "test text");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,66 @@
|
||||||
use editor::{EditorBlurred, EditorCreated, EditorFocused, EditorMode, EditorReleased};
|
use editor::{EditorBlurred, EditorFocused, EditorMode, EditorReleased, Event};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
|
|
||||||
use crate::{state::Mode, Vim};
|
use crate::{state::Mode, Vim};
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.subscribe_global(editor_created).detach();
|
cx.subscribe_global(focused).detach();
|
||||||
cx.subscribe_global(editor_focused).detach();
|
cx.subscribe_global(blurred).detach();
|
||||||
cx.subscribe_global(editor_blurred).detach();
|
cx.subscribe_global(released).detach();
|
||||||
cx.subscribe_global(editor_released).detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_created(EditorCreated(editor): &EditorCreated, cx: &mut MutableAppContext) {
|
fn focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppContext) {
|
||||||
cx.update_default_global(|vim: &mut Vim, cx| {
|
|
||||||
vim.editors.insert(editor.id(), editor.downgrade());
|
|
||||||
vim.sync_vim_settings(cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppContext) {
|
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
|
if let Some(previously_active_editor) = vim
|
||||||
|
.active_editor
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|editor| editor.upgrade(cx))
|
||||||
|
{
|
||||||
|
vim.unhook_vim_settings(previously_active_editor, cx);
|
||||||
|
}
|
||||||
|
|
||||||
vim.active_editor = Some(editor.downgrade());
|
vim.active_editor = Some(editor.downgrade());
|
||||||
vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| {
|
vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| match event {
|
||||||
if editor.read(cx).leader_replica_id().is_none() {
|
Event::SelectionsChanged { local: true } => {
|
||||||
if let editor::Event::SelectionsChanged { local: true } = event {
|
let editor = editor.read(cx);
|
||||||
let newest_empty = editor.read(cx).selections.newest::<usize>(cx).is_empty();
|
if editor.leader_replica_id().is_none() {
|
||||||
editor_local_selections_changed(newest_empty, cx);
|
let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
|
||||||
|
local_selections_changed(newest_empty, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::InputIgnored { text } => {
|
||||||
|
Vim::active_editor_input_ignored(text.clone(), cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if !vim.enabled {
|
if vim.enabled {
|
||||||
return;
|
let editor = editor.read(cx);
|
||||||
|
let editor_mode = editor.mode();
|
||||||
|
let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
|
||||||
|
|
||||||
|
if editor_mode == EditorMode::Full && !newest_selection_empty {
|
||||||
|
vim.switch_mode(Mode::Visual { line: false }, true, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let editor = editor.read(cx);
|
vim.sync_vim_settings(cx);
|
||||||
let editor_mode = editor.mode();
|
|
||||||
let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
|
|
||||||
|
|
||||||
if editor_mode == EditorMode::Full && !newest_selection_empty {
|
|
||||||
vim.switch_mode(Mode::Visual { line: false }, true, cx);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut MutableAppContext) {
|
fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut MutableAppContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
if let Some(previous_editor) = vim.active_editor.clone() {
|
if let Some(previous_editor) = vim.active_editor.clone() {
|
||||||
if previous_editor == editor.clone() {
|
if previous_editor == editor.clone() {
|
||||||
vim.active_editor = None;
|
vim.active_editor = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vim.sync_vim_settings(cx);
|
vim.unhook_vim_settings(editor.clone(), cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppContext) {
|
fn released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppContext) {
|
||||||
cx.update_default_global(|vim: &mut Vim, _| {
|
cx.update_default_global(|vim: &mut Vim, _| {
|
||||||
vim.editors.remove(&editor.id());
|
|
||||||
if let Some(previous_editor) = vim.active_editor.clone() {
|
if let Some(previous_editor) = vim.active_editor.clone() {
|
||||||
if previous_editor == editor.clone() {
|
if previous_editor == editor.clone() {
|
||||||
vim.active_editor = None;
|
vim.active_editor = None;
|
||||||
|
@ -65,7 +69,7 @@ fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppC
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_local_selections_changed(newest_empty: bool, cx: &mut MutableAppContext) {
|
fn local_selections_changed(newest_empty: bool, cx: &mut MutableAppContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
|
if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
|
||||||
vim.switch_mode(Mode::Visual { line: false }, false, cx)
|
vim.switch_mode(Mode::Visual { line: false }, false, cx)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use editor::{
|
use editor::{
|
||||||
char_kind,
|
char_kind,
|
||||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
|
@ -15,7 +17,7 @@ use crate::{
|
||||||
Vim,
|
Vim,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Motion {
|
pub enum Motion {
|
||||||
Left,
|
Left,
|
||||||
Backspace,
|
Backspace,
|
||||||
|
@ -32,8 +34,8 @@ pub enum Motion {
|
||||||
StartOfDocument,
|
StartOfDocument,
|
||||||
EndOfDocument,
|
EndOfDocument,
|
||||||
Matching,
|
Matching,
|
||||||
FindForward { before: bool, character: char },
|
FindForward { before: bool, text: Arc<str> },
|
||||||
FindBackward { after: bool, character: char },
|
FindBackward { after: bool, text: Arc<str> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
|
@ -134,7 +136,7 @@ pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
|
||||||
// Motion handling is specified here:
|
// Motion handling is specified here:
|
||||||
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
|
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
|
||||||
impl Motion {
|
impl Motion {
|
||||||
pub fn linewise(self) -> bool {
|
pub fn linewise(&self) -> bool {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
|
@ -142,12 +144,12 @@ impl Motion {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn infallible(self) -> bool {
|
pub fn infallible(&self) -> bool {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
matches!(self, StartOfDocument | CurrentLine | EndOfDocument)
|
matches!(self, StartOfDocument | CurrentLine | EndOfDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inclusive(self) -> bool {
|
pub fn inclusive(&self) -> bool {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
match self {
|
match self {
|
||||||
Down
|
Down
|
||||||
|
@ -171,13 +173,14 @@ impl Motion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_point(
|
pub fn move_point(
|
||||||
self,
|
&self,
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
point: DisplayPoint,
|
point: DisplayPoint,
|
||||||
goal: SelectionGoal,
|
goal: SelectionGoal,
|
||||||
times: usize,
|
times: usize,
|
||||||
) -> Option<(DisplayPoint, SelectionGoal)> {
|
) -> Option<(DisplayPoint, SelectionGoal)> {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
|
let infallible = self.infallible();
|
||||||
let (new_point, goal) = match self {
|
let (new_point, goal) = match self {
|
||||||
Left => (left(map, point, times), SelectionGoal::None),
|
Left => (left(map, point, times), SelectionGoal::None),
|
||||||
Backspace => (backspace(map, point, times), SelectionGoal::None),
|
Backspace => (backspace(map, point, times), SelectionGoal::None),
|
||||||
|
@ -185,15 +188,15 @@ impl Motion {
|
||||||
Up => up(map, point, goal, times),
|
Up => up(map, point, goal, times),
|
||||||
Right => (right(map, point, times), SelectionGoal::None),
|
Right => (right(map, point, times), SelectionGoal::None),
|
||||||
NextWordStart { ignore_punctuation } => (
|
NextWordStart { ignore_punctuation } => (
|
||||||
next_word_start(map, point, ignore_punctuation, times),
|
next_word_start(map, point, *ignore_punctuation, times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
NextWordEnd { ignore_punctuation } => (
|
NextWordEnd { ignore_punctuation } => (
|
||||||
next_word_end(map, point, ignore_punctuation, times),
|
next_word_end(map, point, *ignore_punctuation, times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
PreviousWordStart { ignore_punctuation } => (
|
PreviousWordStart { ignore_punctuation } => (
|
||||||
previous_word_start(map, point, ignore_punctuation, times),
|
previous_word_start(map, point, *ignore_punctuation, times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None),
|
FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None),
|
||||||
|
@ -203,22 +206,22 @@ impl Motion {
|
||||||
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
|
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
|
||||||
EndOfDocument => (end_of_document(map, point, times), SelectionGoal::None),
|
EndOfDocument => (end_of_document(map, point, times), SelectionGoal::None),
|
||||||
Matching => (matching(map, point), SelectionGoal::None),
|
Matching => (matching(map, point), SelectionGoal::None),
|
||||||
FindForward { before, character } => (
|
FindForward { before, text } => (
|
||||||
find_forward(map, point, before, character, times),
|
find_forward(map, point, *before, text.clone(), times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
FindBackward { after, character } => (
|
FindBackward { after, text } => (
|
||||||
find_backward(map, point, after, character, times),
|
find_backward(map, point, *after, text.clone(), times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
(new_point != point || self.infallible()).then_some((new_point, goal))
|
(new_point != point || infallible).then_some((new_point, goal))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expands a selection using self motion for an operator
|
// Expands a selection using self motion for an operator
|
||||||
pub fn expand_selection(
|
pub fn expand_selection(
|
||||||
self,
|
&self,
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
selection: &mut Selection<DisplayPoint>,
|
selection: &mut Selection<DisplayPoint>,
|
||||||
times: usize,
|
times: usize,
|
||||||
|
@ -254,7 +257,7 @@ impl Motion {
|
||||||
// but "d}" will not include that line.
|
// but "d}" will not include that line.
|
||||||
let mut inclusive = self.inclusive();
|
let mut inclusive = self.inclusive();
|
||||||
if !inclusive
|
if !inclusive
|
||||||
&& self != Motion::Backspace
|
&& self != &Motion::Backspace
|
||||||
&& selection.end.row() > selection.start.row()
|
&& selection.end.row() > selection.start.row()
|
||||||
&& selection.end.column() == 0
|
&& selection.end.column() == 0
|
||||||
{
|
{
|
||||||
|
@ -466,45 +469,42 @@ fn find_forward(
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
from: DisplayPoint,
|
from: DisplayPoint,
|
||||||
before: bool,
|
before: bool,
|
||||||
target: char,
|
target: Arc<str>,
|
||||||
mut times: usize,
|
times: usize,
|
||||||
) -> DisplayPoint {
|
) -> DisplayPoint {
|
||||||
let mut previous_point = from;
|
map.find_while(from, target.as_ref(), |ch, _| ch != '\n')
|
||||||
|
.skip_while(|found_at| found_at == &from)
|
||||||
for (ch, point) in map.chars_at(from) {
|
.nth(times - 1)
|
||||||
if ch == target && point != from {
|
.map(|mut found| {
|
||||||
times -= 1;
|
if before {
|
||||||
if times == 0 {
|
*found.column_mut() -= 1;
|
||||||
return if before { previous_point } else { point };
|
found = map.clip_point(found, Bias::Right);
|
||||||
|
found
|
||||||
|
} else {
|
||||||
|
found
|
||||||
}
|
}
|
||||||
} else if ch == '\n' {
|
})
|
||||||
break;
|
.unwrap_or(from)
|
||||||
}
|
|
||||||
previous_point = point;
|
|
||||||
}
|
|
||||||
|
|
||||||
from
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_backward(
|
fn find_backward(
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
from: DisplayPoint,
|
from: DisplayPoint,
|
||||||
after: bool,
|
after: bool,
|
||||||
target: char,
|
target: Arc<str>,
|
||||||
mut times: usize,
|
times: usize,
|
||||||
) -> DisplayPoint {
|
) -> DisplayPoint {
|
||||||
let mut previous_point = from;
|
map.reverse_find_while(from, target.as_ref(), |ch, _| ch != '\n')
|
||||||
for (ch, point) in map.reverse_chars_at(from) {
|
.skip_while(|found_at| found_at == &from)
|
||||||
if ch == target && point != from {
|
.nth(times - 1)
|
||||||
times -= 1;
|
.map(|mut found| {
|
||||||
if times == 0 {
|
if after {
|
||||||
return if after { previous_point } else { point };
|
*found.column_mut() += 1;
|
||||||
|
found = map.clip_point(found, Bias::Left);
|
||||||
|
found
|
||||||
|
} else {
|
||||||
|
found
|
||||||
}
|
}
|
||||||
} else if ch == '\n' {
|
})
|
||||||
break;
|
.unwrap_or(from)
|
||||||
}
|
|
||||||
previous_point = point;
|
|
||||||
}
|
|
||||||
|
|
||||||
from
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod change;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod yank;
|
mod yank;
|
||||||
|
|
||||||
use std::{borrow::Cow, cmp::Ordering};
|
use std::{borrow::Cow, cmp::Ordering, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::Motion,
|
motion::Motion,
|
||||||
|
@ -424,7 +424,7 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Edito
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) {
|
pub(crate) fn normal_replace(text: Arc<str>, cx: &mut MutableAppContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
|
@ -453,7 +453,7 @@ pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) {
|
||||||
(
|
(
|
||||||
range.start.to_offset(&map, Bias::Left)
|
range.start.to_offset(&map, Bias::Left)
|
||||||
..range.end.to_offset(&map, Bias::Left),
|
..range.end.to_offset(&map, Bias::Left),
|
||||||
text,
|
text.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -53,7 +53,7 @@ impl<'a> VimTestContext<'a> {
|
||||||
|
|
||||||
// Setup search toolbars and keypress hook
|
// Setup search toolbars and keypress hook
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
observe_keypresses(window_id, cx);
|
observe_keystrokes(window_id, cx);
|
||||||
workspace.active_pane().update(cx, |pane, cx| {
|
workspace.active_pane().update(cx, |pane, cx| {
|
||||||
pane.toolbar().update(cx, |toolbar, cx| {
|
pane.toolbar().update(cx, |toolbar, cx| {
|
||||||
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
|
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
|
||||||
|
|
|
@ -10,13 +10,12 @@ mod state;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod visual;
|
mod visual;
|
||||||
|
|
||||||
use collections::HashMap;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use command_palette::CommandPaletteFilter;
|
use command_palette::CommandPaletteFilter;
|
||||||
use editor::{Bias, Cancel, Editor, EditorMode};
|
use editor::{Bias, Cancel, Editor, EditorMode};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
impl_actions,
|
actions, impl_actions, MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
keymap_matcher::{KeyPressed, Keystroke},
|
|
||||||
MutableAppContext, Subscription, ViewContext, WeakViewHandle,
|
|
||||||
};
|
};
|
||||||
use language::CursorShape;
|
use language::CursorShape;
|
||||||
use motion::Motion;
|
use motion::Motion;
|
||||||
|
@ -36,6 +35,7 @@ pub struct PushOperator(pub Operator);
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
struct Number(u8);
|
struct Number(u8);
|
||||||
|
|
||||||
|
actions!(vim, [Tab, Enter]);
|
||||||
impl_actions!(vim, [Number, SwitchMode, PushOperator]);
|
impl_actions!(vim, [Number, SwitchMode, PushOperator]);
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
@ -58,11 +58,6 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
|
cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
|
||||||
Vim::update(cx, |vim, cx| vim.push_number(n, cx));
|
Vim::update(cx, |vim, cx| vim.push_number(n, cx));
|
||||||
});
|
});
|
||||||
cx.add_action(
|
|
||||||
|_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| {
|
|
||||||
Vim::key_pressed(keystroke, cx);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Editor Actions
|
// Editor Actions
|
||||||
cx.add_action(|_: &mut Editor, _: &Cancel, cx| {
|
cx.add_action(|_: &mut Editor, _: &Cancel, cx| {
|
||||||
|
@ -80,8 +75,16 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cx.add_action(|_: &mut Workspace, _: &Tab, cx| {
|
||||||
|
Vim::active_editor_input_ignored(" ".into(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.add_action(|_: &mut Workspace, _: &Enter, cx| {
|
||||||
|
Vim::active_editor_input_ignored("\n".into(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
// Sync initial settings with the rest of the app
|
// Sync initial settings with the rest of the app
|
||||||
Vim::update(cx, |state, cx| state.sync_vim_settings(cx));
|
Vim::update(cx, |vim, cx| vim.sync_vim_settings(cx));
|
||||||
|
|
||||||
// Any time settings change, update vim mode to match
|
// Any time settings change, update vim mode to match
|
||||||
cx.observe_global::<Settings, _>(|cx| {
|
cx.observe_global::<Settings, _>(|cx| {
|
||||||
|
@ -92,7 +95,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) {
|
pub fn observe_keystrokes(window_id: usize, cx: &mut MutableAppContext) {
|
||||||
cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| {
|
cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| {
|
||||||
if let Some(handled_by) = handled_by {
|
if let Some(handled_by) = handled_by {
|
||||||
// Keystroke is handled by the vim system, so continue forward
|
// Keystroke is handled by the vim system, so continue forward
|
||||||
|
@ -104,11 +107,14 @@ pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| match vim.active_operator() {
|
||||||
if vim.active_operator().is_some() {
|
Some(
|
||||||
// If the keystroke is not handled by vim, we should clear the operator
|
Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace,
|
||||||
|
) => {}
|
||||||
|
Some(_) => {
|
||||||
vim.clear_operator(cx);
|
vim.clear_operator(cx);
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
|
@ -117,9 +123,8 @@ pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Vim {
|
pub struct Vim {
|
||||||
editors: HashMap<usize, WeakViewHandle<Editor>>,
|
|
||||||
active_editor: Option<WeakViewHandle<Editor>>,
|
active_editor: Option<WeakViewHandle<Editor>>,
|
||||||
selection_subscription: Option<Subscription>,
|
editor_subscription: Option<Subscription>,
|
||||||
|
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: VimState,
|
state: VimState,
|
||||||
|
@ -160,24 +165,26 @@ impl Vim {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust selections
|
// Adjust selections
|
||||||
for editor in self.editors.values() {
|
if let Some(editor) = self
|
||||||
if let Some(editor) = editor.upgrade(cx) {
|
.active_editor
|
||||||
editor.update(cx, |editor, cx| {
|
.as_ref()
|
||||||
editor.change_selections(None, cx, |s| {
|
.and_then(|editor| editor.upgrade(cx))
|
||||||
s.move_with(|map, selection| {
|
{
|
||||||
if self.state.empty_selections_only() {
|
editor.update(cx, |editor, cx| {
|
||||||
let new_head = map.clip_point(selection.head(), Bias::Left);
|
editor.change_selections(None, cx, |s| {
|
||||||
selection.collapse_to(new_head, selection.goal)
|
s.move_with(|map, selection| {
|
||||||
} else {
|
if self.state.empty_selections_only() {
|
||||||
selection.set_head(
|
let new_head = map.clip_point(selection.head(), Bias::Left);
|
||||||
map.clip_point(selection.head(), Bias::Left),
|
selection.collapse_to(new_head, selection.goal)
|
||||||
selection.goal,
|
} else {
|
||||||
);
|
selection.set_head(
|
||||||
}
|
map.clip_point(selection.head(), Bias::Left),
|
||||||
});
|
selection.goal,
|
||||||
})
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,24 +227,24 @@ impl Vim {
|
||||||
self.state.operator_stack.last().copied()
|
self.state.operator_stack.last().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_pressed(keystroke: &Keystroke, cx: &mut ViewContext<Workspace>) {
|
fn active_editor_input_ignored(text: Arc<str>, cx: &mut MutableAppContext) {
|
||||||
|
if text.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match Vim::read(cx).active_operator() {
|
match Vim::read(cx).active_operator() {
|
||||||
Some(Operator::FindForward { before }) => {
|
Some(Operator::FindForward { before }) => {
|
||||||
if let Some(character) = keystroke.key.chars().next() {
|
motion::motion(Motion::FindForward { before, text }, cx)
|
||||||
motion::motion(Motion::FindForward { before, character }, cx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(Operator::FindBackward { after }) => {
|
Some(Operator::FindBackward { after }) => {
|
||||||
if let Some(character) = keystroke.key.chars().next() {
|
motion::motion(Motion::FindBackward { after, text }, cx)
|
||||||
motion::motion(Motion::FindBackward { after, character }, cx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(Operator::Replace) => match Vim::read(cx).state.mode {
|
Some(Operator::Replace) => match Vim::read(cx).state.mode {
|
||||||
Mode::Normal => normal_replace(&keystroke.key, cx),
|
Mode::Normal => normal_replace(text, cx),
|
||||||
Mode::Visual { line } => visual_replace(&keystroke.key, line, cx),
|
Mode::Visual { line } => visual_replace(text, line, cx),
|
||||||
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
||||||
},
|
},
|
||||||
_ => cx.propagate_action(),
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,26 +271,33 @@ impl Vim {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for editor in self.editors.values() {
|
if let Some(editor) = self
|
||||||
if let Some(editor) = editor.upgrade(cx) {
|
.active_editor
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|editor| editor.upgrade(cx))
|
||||||
|
{
|
||||||
|
if self.enabled && editor.read(cx).mode() == EditorMode::Full {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
if self.enabled && editor.mode() == EditorMode::Full {
|
editor.set_cursor_shape(cursor_shape, cx);
|
||||||
editor.set_cursor_shape(cursor_shape, cx);
|
editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
|
||||||
editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
|
editor.set_input_enabled(!state.vim_controlled());
|
||||||
editor.set_input_enabled(!state.vim_controlled());
|
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
||||||
editor.selections.line_mode =
|
let context_layer = state.keymap_context_layer();
|
||||||
matches!(state.mode, Mode::Visual { line: true });
|
editor.set_keymap_context_layer::<Self>(context_layer);
|
||||||
let context_layer = state.keymap_context_layer();
|
|
||||||
editor.set_keymap_context_layer::<Self>(context_layer);
|
|
||||||
} else {
|
|
||||||
editor.set_cursor_shape(CursorShape::Bar, cx);
|
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
|
||||||
editor.set_input_enabled(true);
|
|
||||||
editor.selections.line_mode = false;
|
|
||||||
editor.remove_keymap_context_layer::<Self>();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
self.unhook_vim_settings(editor, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unhook_vim_settings(&self, editor: ViewHandle<Editor>, cx: &mut MutableAppContext) {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_cursor_shape(CursorShape::Bar, cx);
|
||||||
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
editor.set_input_enabled(true);
|
||||||
|
editor.selections.line_mode = false;
|
||||||
|
editor.remove_keymap_context_layer::<Self>();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
|
@ -313,7 +313,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) {
|
pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut MutableAppContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
|
|
|
@ -363,7 +363,7 @@ pub fn initialize_workspace(
|
||||||
auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
|
auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
|
||||||
|
|
||||||
let window_id = cx.window_id();
|
let window_id = cx.window_id();
|
||||||
vim::observe_keypresses(window_id, cx);
|
vim::observe_keystrokes(window_id, cx);
|
||||||
|
|
||||||
cx.on_window_should_close(|workspace, cx| {
|
cx.on_window_should_close(|workspace, cx| {
|
||||||
if let Some(task) = workspace.close(&Default::default(), cx) {
|
if let Some(task) = workspace.close(&Default::default(), cx) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue