More vim fixes and move some more things out of app.rs

This commit is contained in:
Kay Simmons 2023-02-10 14:41:22 -08:00
parent c1812ddc27
commit 3d53336916
19 changed files with 595 additions and 473 deletions

1
Cargo.lock generated
View file

@ -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",
] ]

View file

@ -315,7 +315,7 @@
{ {
"context": "Editor && VimWaiting", "context": "Editor && VimWaiting",
"bindings": { "bindings": {
"*": "gpui::KeyPressed", // "*": "gpui::KeyPressed",
"escape": "editor::Cancel" "escape": "editor::Cancel"
} }
} }

View file

@ -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>,

View file

@ -6400,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);

View file

@ -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();
@ -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::*;

View 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);
}
}));
}

View 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);
}
}

View file

@ -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;

View 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)
}
}

View file

@ -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,

View file

@ -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() {

View file

@ -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"}

View file

@ -1,4 +1,4 @@
use editor::{EditorBlurred, 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};
@ -20,14 +20,18 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppContext) {
} }
vim.active_editor = Some(editor.downgrade()); vim.active_editor = Some(editor.downgrade());
dbg!("Active editor changed", editor.read(cx).mode()); vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| match event {
vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| { Event::SelectionsChanged { local: true } => {
if editor.read(cx).leader_replica_id().is_none() { let editor = editor.read(cx);
if let editor::Event::SelectionsChanged { local: true } = event { if editor.leader_replica_id().is_none() {
let newest_empty = editor.read(cx).selections.newest::<usize>(cx).is_empty(); let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
local_selections_changed(newest_empty, cx); local_selections_changed(newest_empty, cx);
} }
} }
Event::InputIgnored { text } => {
Vim::active_editor_input_ignored(text.clone(), cx);
}
_ => {}
})); }));
if vim.enabled { if vim.enabled {

View file

@ -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
} }

View file

@ -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<_>>();

View file

@ -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);

View file

@ -10,12 +10,12 @@ mod state;
mod utils; mod utils;
mod visual; mod visual;
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, impl_actions, MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle,
keymap_matcher::{KeyPressed, Keystroke},
MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle,
}; };
use language::CursorShape; use language::CursorShape;
use motion::Motion; use motion::Motion;
@ -57,11 +57,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| {
@ -91,7 +86,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
@ -103,11 +98,12 @@ 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(Operator::FindForward { .. } | Operator::FindBackward { .. }) => {}
// If the keystroke is not handled by vim, we should clear the operator Some(_) => {
vim.clear_operator(cx); vim.clear_operator(cx);
} }
_ => {}
}); });
true true
}) })
@ -164,7 +160,6 @@ impl Vim {
.and_then(|editor| editor.upgrade(cx)) .and_then(|editor| editor.upgrade(cx))
{ {
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
dbg!(&mode, editor.mode());
editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| { s.move_with(|map, selection| {
if self.state.empty_selections_only() { if self.state.empty_selections_only() {
@ -221,24 +216,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(), _ => {}
} }
} }

View file

@ -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| {

View file

@ -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) {