Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:33:32.138Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

state.rs

1948 lines · 67.8 KB · rust
1use crate::command::command_interceptor;
2use crate::motion::MotionKind;
3use crate::normal::repeat::Replayer;
4use crate::surrounds::SurroundsType;
5use crate::{ToggleMarksView, ToggleRegistersView, UseSystemClipboard, Vim, VimAddon, VimSettings};
6use crate::{motion::Motion, object::Object};
7use anyhow::Result;
8use collections::HashMap;
9use command_palette_hooks::{CommandPaletteFilter, GlobalCommandPaletteInterceptor};
10use db::{
11    sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
12    sqlez_macros::sql,
13};
14use editor::display_map::{is_invisible, replacement};
15use editor::{Anchor, ClipboardSelection, Editor, MultiBuffer, ToPoint as EditorToPoint};
16use gpui::{
17    Action, App, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, DismissEvent, Entity,
18    EntityId, Global, HighlightStyle, StyledText, Subscription, Task, TaskExt, TextStyle,
19    WeakEntity,
20};
21use language::{Buffer, BufferEvent, BufferId, Chunk, LanguageAwareStyling, Point};
22
23use multi_buffer::MultiBufferRow;
24use picker::{Picker, PickerDelegate};
25use project::{Project, ProjectItem, ProjectPath};
26use serde::{Deserialize, Serialize};
27use settings::{Settings, SettingsStore};
28use std::borrow::BorrowMut;
29use std::collections::HashSet;
30use std::path::Path;
31use std::{fmt::Display, ops::Range, sync::Arc};
32use text::{Bias, ToPoint};
33use theme_settings::ThemeSettings;
34use ui::{
35    ActiveTheme, Context, Div, FluentBuilder, KeyBinding, ParentElement, SharedString, Styled,
36    StyledTypography, Window, h_flex, rems,
37};
38use util::ResultExt;
39use util::rel_path::RelPath;
40use workspace::searchable::Direction;
41use workspace::{MultiWorkspace, Workspace, WorkspaceDb, WorkspaceId};
42
43#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)]
44pub enum Mode {
45    #[default]
46    Normal,
47    Insert,
48    Replace,
49    Visual,
50    VisualLine,
51    VisualBlock,
52    HelixNormal,
53    HelixSelect,
54}
55
56impl Display for Mode {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            Mode::Normal => write!(f, "NORMAL"),
60            Mode::Insert => write!(f, "INSERT"),
61            Mode::Replace => write!(f, "REPLACE"),
62            Mode::Visual => write!(f, "VISUAL"),
63            Mode::VisualLine => write!(f, "VISUAL LINE"),
64            Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
65            Mode::HelixNormal => write!(f, "NORMAL"),
66            Mode::HelixSelect => write!(f, "SELECT"),
67        }
68    }
69}
70
71impl Mode {
72    pub fn is_visual(&self) -> bool {
73        match self {
74            Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true,
75            Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
76        }
77    }
78
79    pub fn is_helix(&self) -> bool {
80        matches!(self, Self::HelixNormal | Self::HelixSelect)
81    }
82
83    /// `HelixNormal` qualifies because its cursor is itself a one-character selection.
84    pub fn has_selection(&self) -> bool {
85        self.is_visual() || matches!(self, Self::HelixNormal)
86    }
87
88    pub fn is_normal(&self) -> bool {
89        matches!(self, Self::Normal | Self::HelixNormal)
90    }
91}
92
93#[derive(Clone, Debug, PartialEq)]
94pub enum Operator {
95    Change,
96    Delete,
97    Yank,
98    Replace,
99    Object {
100        around: bool,
101    },
102    FindForward {
103        before: bool,
104        multiline: bool,
105    },
106    FindBackward {
107        after: bool,
108        multiline: bool,
109    },
110    Sneak {
111        first_char: Option<char>,
112    },
113    SneakBackward {
114        first_char: Option<char>,
115    },
116    AddSurrounds {
117        // Typically no need to configure this as `SendKeystrokes` can be used - see #23088.
118        target: Option<SurroundsType>,
119    },
120    ChangeSurrounds {
121        target: Option<Object>,
122        /// Represents whether the opening bracket was used for the target
123        /// object.
124        opening: bool,
125        /// Computed anchors for the opening and closing bracket characters,
126        bracket_anchors: Vec<Option<(Anchor, Anchor)>>,
127    },
128    DeleteSurrounds,
129    Mark,
130    Jump {
131        line: bool,
132    },
133    Indent,
134    Outdent,
135    AutoIndent,
136    Rewrap,
137    ShellCommand,
138    Lowercase,
139    Uppercase,
140    OppositeCase,
141    Rot13,
142    Rot47,
143    Digraph {
144        first_char: Option<char>,
145    },
146    Literal {
147        prefix: Option<String>,
148    },
149    Register,
150    RecordRegister,
151    ReplayRegister,
152    ToggleComments,
153    ToggleBlockComments,
154    ReplaceWithRegister,
155    Exchange,
156    HelixMatch,
157    HelixNext {
158        around: bool,
159    },
160    HelixPrevious {
161        around: bool,
162    },
163    HelixSurroundAdd,
164    HelixSurroundReplace {
165        replaced_char: Option<char>,
166    },
167    HelixSurroundDelete,
168    HelixJump {
169        behaviour: HelixJumpBehaviour,
170        first_char: Option<char>,
171        labels: Vec<HelixJumpLabel>,
172    },
173}
174
175#[derive(Clone, Debug, PartialEq)]
176pub struct HelixJumpLabel {
177    pub label: [char; 2],
178    pub range: Range<Anchor>,
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182pub enum HelixJumpBehaviour {
183    Move,
184    MoveToWordStart,
185    Extend,
186    ExtendToWordStart,
187}
188
189#[derive(Default, Clone, Debug)]
190pub enum RecordedSelection {
191    #[default]
192    None,
193    Visual {
194        rows: u32,
195        cols: u32,
196    },
197    SingleLine {
198        cols: u32,
199    },
200    VisualBlock {
201        rows: u32,
202        cols: u32,
203    },
204    VisualLine {
205        rows: u32,
206    },
207}
208
209#[derive(Default, Clone, Debug)]
210pub struct Register {
211    pub(crate) text: SharedString,
212    pub(crate) clipboard_selections: Option<Vec<ClipboardSelection>>,
213}
214
215impl From<Register> for ClipboardItem {
216    fn from(register: Register) -> Self {
217        if let Some(clipboard_selections) = register.clipboard_selections {
218            ClipboardItem::new_string_with_json_metadata(register.text.into(), clipboard_selections)
219        } else {
220            ClipboardItem::new_string(register.text.into())
221        }
222    }
223}
224
225impl From<ClipboardItem> for Register {
226    fn from(item: ClipboardItem) -> Self {
227        match item.entries().iter().find_map(|entry| match entry {
228            ClipboardEntry::String(value) => Some(value),
229            _ => None,
230        }) {
231            Some(value) => Register {
232                text: value.text().to_owned().into(),
233                clipboard_selections: value.metadata_json::<Vec<ClipboardSelection>>(),
234            },
235            None => Register::default(),
236        }
237    }
238}
239
240impl From<String> for Register {
241    fn from(text: String) -> Self {
242        Register {
243            text: text.into(),
244            clipboard_selections: None,
245        }
246    }
247}
248
249#[derive(Default)]
250pub struct VimGlobals {
251    pub last_find: Option<Motion>,
252
253    pub dot_recording: bool,
254    pub dot_replaying: bool,
255
256    /// pre_count is the number before an operator is specified (3 in 3d2d)
257    pub pre_count: Option<usize>,
258    /// post_count is the number after an operator is specified (2 in 3d2d)
259    pub post_count: Option<usize>,
260    pub forced_motion: bool,
261    pub stop_recording_after_next_action: bool,
262    pub ignore_current_insertion: bool,
263    pub recording_count: Option<usize>,
264    pub recorded_count: Option<usize>,
265    pub recording_actions: Vec<ReplayableAction>,
266    pub recorded_actions: Vec<ReplayableAction>,
267    pub recorded_selection: RecordedSelection,
268
269    /// The register being written to by the active `q{register}` macro
270    /// recording.
271    pub recording_register: Option<char>,
272    /// The register that was selected at the start of the current
273    /// dot-recording, for example, `"ap`.
274    pub recording_register_for_dot: Option<char>,
275    /// The register from the last completed dot-recording. Used when replaying
276    /// with `.`.
277    pub recorded_register_for_dot: Option<char>,
278    pub last_recorded_register: Option<char>,
279    pub last_replayed_register: Option<char>,
280    pub replayer: Option<Replayer>,
281
282    pub last_yank: Option<SharedString>,
283    pub registers: HashMap<char, Register>,
284    pub recordings: HashMap<char, Vec<ReplayableAction>>,
285
286    pub focused_vim: Option<WeakEntity<Vim>>,
287
288    pub marks: HashMap<EntityId, Entity<MarksState>>,
289}
290
291pub struct MarksState {
292    workspace: WeakEntity<Workspace>,
293
294    multibuffer_marks: HashMap<EntityId, HashMap<String, Vec<Anchor>>>,
295    buffer_marks: HashMap<BufferId, HashMap<String, Vec<text::Anchor>>>,
296    watched_buffers: HashMap<BufferId, (MarkLocation, Subscription, Subscription)>,
297
298    serialized_marks: HashMap<Arc<Path>, HashMap<String, Vec<Point>>>,
299    global_marks: HashMap<String, MarkLocation>,
300
301    _subscription: Subscription,
302}
303
304#[derive(Debug, PartialEq, Eq, Clone)]
305pub enum MarkLocation {
306    Buffer(EntityId),
307    Path(Arc<Path>),
308}
309
310pub enum Mark {
311    Local(Vec<Anchor>),
312    Buffer(EntityId, Vec<Anchor>),
313    Path(Arc<Path>, Vec<Point>),
314}
315
316impl MarksState {
317    pub fn new(workspace: &Workspace, cx: &mut App) -> Entity<MarksState> {
318        cx.new(|cx| {
319            let buffer_store = workspace.project().read(cx).buffer_store().clone();
320            let subscription = cx.subscribe(&buffer_store, move |this: &mut Self, _, event, cx| {
321                if let project::buffer_store::BufferStoreEvent::BufferAdded(buffer) = event {
322                    this.on_buffer_loaded(buffer, cx);
323                }
324            });
325
326            let mut this = Self {
327                workspace: workspace.weak_handle(),
328                multibuffer_marks: HashMap::default(),
329                buffer_marks: HashMap::default(),
330                watched_buffers: HashMap::default(),
331                serialized_marks: HashMap::default(),
332                global_marks: HashMap::default(),
333                _subscription: subscription,
334            };
335
336            this.load(cx);
337            this
338        })
339    }
340
341    fn workspace_id(&self, cx: &App) -> Option<WorkspaceId> {
342        self.workspace
343            .read_with(cx, |workspace, _| workspace.database_id())
344            .ok()
345            .flatten()
346    }
347
348    fn project(&self, cx: &App) -> Option<Entity<Project>> {
349        self.workspace
350            .read_with(cx, |workspace, _| workspace.project().clone())
351            .ok()
352    }
353
354    fn load(&mut self, cx: &mut Context<Self>) {
355        cx.spawn(async move |this, cx| {
356            let Some(workspace_id) = this.update(cx, |this, cx| this.workspace_id(cx)).ok()? else {
357                return None;
358            };
359            let db = cx.update(|cx| VimDb::global(cx));
360            let (marks, paths) = cx
361                .background_spawn(async move {
362                    let marks = db.get_marks(workspace_id)?;
363                    let paths = db.get_global_marks_paths(workspace_id)?;
364                    anyhow::Ok((marks, paths))
365                })
366                .await
367                .log_err()?;
368            this.update(cx, |this, cx| this.loaded(marks, paths, cx))
369                .ok()
370        })
371        .detach();
372    }
373
374    fn loaded(
375        &mut self,
376        marks: Vec<SerializedMark>,
377        global_mark_paths: Vec<(String, Arc<Path>)>,
378        cx: &mut Context<Self>,
379    ) {
380        let Some(project) = self.project(cx) else {
381            return;
382        };
383
384        for mark in marks {
385            self.serialized_marks
386                .entry(mark.path)
387                .or_default()
388                .insert(mark.name, mark.points);
389        }
390
391        for (name, path) in global_mark_paths {
392            self.global_marks
393                .insert(name, MarkLocation::Path(path.clone()));
394
395            let project_path = project
396                .read(cx)
397                .worktrees(cx)
398                .filter_map(|worktree| {
399                    let relative = path.strip_prefix(worktree.read(cx).abs_path()).ok()?;
400                    let path = RelPath::new(relative, worktree.read(cx).path_style()).log_err()?;
401                    Some(ProjectPath {
402                        worktree_id: worktree.read(cx).id(),
403                        path: path.into_arc(),
404                    })
405                })
406                .next();
407            if let Some(buffer) = project_path
408                .and_then(|project_path| project.read(cx).get_open_buffer(&project_path, cx))
409            {
410                self.on_buffer_loaded(&buffer, cx)
411            }
412        }
413    }
414
415    pub fn on_buffer_loaded(&mut self, buffer_handle: &Entity<Buffer>, cx: &mut Context<Self>) {
416        let Some(project) = self.project(cx) else {
417            return;
418        };
419        let Some(project_path) = buffer_handle.read(cx).project_path(cx) else {
420            return;
421        };
422        let Some(abs_path) = project.read(cx).absolute_path(&project_path, cx) else {
423            return;
424        };
425        let abs_path: Arc<Path> = abs_path.into();
426
427        let Some(serialized_marks) = self.serialized_marks.get(&abs_path) else {
428            return;
429        };
430
431        let mut loaded_marks = HashMap::default();
432        let buffer = buffer_handle.read(cx);
433        for (name, points) in serialized_marks.iter() {
434            loaded_marks.insert(
435                name.clone(),
436                points
437                    .iter()
438                    .map(|point| buffer.anchor_before(buffer.clip_point(*point, Bias::Left)))
439                    .collect(),
440            );
441        }
442        self.buffer_marks.insert(buffer.remote_id(), loaded_marks);
443        self.watch_buffer(MarkLocation::Path(abs_path), buffer_handle, cx)
444    }
445
446    fn serialize_buffer_marks(
447        &mut self,
448        path: Arc<Path>,
449        buffer: &Entity<Buffer>,
450        cx: &mut Context<Self>,
451    ) {
452        let new_points: HashMap<String, Vec<Point>> =
453            if let Some(anchors) = self.buffer_marks.get(&buffer.read(cx).remote_id()) {
454                anchors
455                    .iter()
456                    .map(|(name, anchors)| {
457                        (
458                            name.clone(),
459                            buffer
460                                .read(cx)
461                                .summaries_for_anchors::<Point, _>(anchors.iter().copied())
462                                .collect(),
463                        )
464                    })
465                    .collect()
466            } else {
467                HashMap::default()
468            };
469        let old_points = self.serialized_marks.get(&path);
470        if old_points == Some(&new_points) {
471            return;
472        }
473        let mut to_write = HashMap::default();
474
475        for (key, value) in &new_points {
476            if self.is_global_mark(key)
477                && self.global_marks.get(key) != Some(&MarkLocation::Path(path.clone()))
478            {
479                if let Some(workspace_id) = self.workspace_id(cx) {
480                    let path = path.clone();
481                    let key = key.clone();
482                    let db = VimDb::global(cx);
483                    cx.background_spawn(async move {
484                        db.set_global_mark_path(workspace_id, key, path).await
485                    })
486                    .detach_and_log_err(cx);
487                }
488
489                self.global_marks
490                    .insert(key.clone(), MarkLocation::Path(path.clone()));
491            }
492            if old_points.and_then(|o| o.get(key)) != Some(value) {
493                to_write.insert(key.clone(), value.clone());
494            }
495        }
496
497        self.serialized_marks.insert(path.clone(), new_points);
498
499        if let Some(workspace_id) = self.workspace_id(cx) {
500            let db = VimDb::global(cx);
501            cx.background_spawn(async move {
502                db.set_marks(workspace_id, path.clone(), to_write).await?;
503                anyhow::Ok(())
504            })
505            .detach_and_log_err(cx);
506        }
507    }
508
509    fn is_global_mark(&self, key: &str) -> bool {
510        key.chars()
511            .next()
512            .is_some_and(|c| c.is_uppercase() || c.is_digit(10))
513    }
514
515    fn rename_buffer(
516        &mut self,
517        old_path: MarkLocation,
518        new_path: Arc<Path>,
519        buffer: &Entity<Buffer>,
520        cx: &mut Context<Self>,
521    ) {
522        if let MarkLocation::Buffer(entity_id) = old_path
523            && let Some(old_marks) = self.multibuffer_marks.remove(&entity_id)
524        {
525            let buffer_marks = old_marks
526                .into_iter()
527                .map(|(k, v)| {
528                    (
529                        k,
530                        v.into_iter()
531                            .filter_map(|anchor| anchor.raw_text_anchor())
532                            .collect(),
533                    )
534                })
535                .collect();
536            self.buffer_marks
537                .insert(buffer.read(cx).remote_id(), buffer_marks);
538        }
539        self.watch_buffer(MarkLocation::Path(new_path.clone()), buffer, cx);
540        self.serialize_buffer_marks(new_path, buffer, cx);
541    }
542
543    fn path_for_buffer(&self, buffer: &Entity<Buffer>, cx: &App) -> Option<Arc<Path>> {
544        let project_path = buffer.read(cx).project_path(cx)?;
545        let project = self.project(cx)?;
546        let abs_path = project.read(cx).absolute_path(&project_path, cx)?;
547        Some(abs_path.into())
548    }
549
550    fn points_at(
551        &self,
552        location: &MarkLocation,
553        multi_buffer: &Entity<MultiBuffer>,
554        cx: &App,
555    ) -> bool {
556        match location {
557            MarkLocation::Buffer(entity_id) => entity_id == &multi_buffer.entity_id(),
558            MarkLocation::Path(path) => {
559                let Some(singleton) = multi_buffer.read(cx).as_singleton() else {
560                    return false;
561                };
562                self.path_for_buffer(&singleton, cx).as_ref() == Some(path)
563            }
564        }
565    }
566
567    pub fn watch_buffer(
568        &mut self,
569        mark_location: MarkLocation,
570        buffer_handle: &Entity<Buffer>,
571        cx: &mut Context<Self>,
572    ) {
573        let on_change = cx.subscribe(buffer_handle, move |this, buffer, event, cx| match event {
574            BufferEvent::Edited { .. } => {
575                if let Some(path) = this.path_for_buffer(&buffer, cx) {
576                    this.serialize_buffer_marks(path, &buffer, cx);
577                }
578            }
579            BufferEvent::FileHandleChanged => {
580                let buffer_id = buffer.read(cx).remote_id();
581                if let Some(old_path) = this
582                    .watched_buffers
583                    .get(&buffer_id.clone())
584                    .map(|(path, _, _)| path.clone())
585                    && let Some(new_path) = this.path_for_buffer(&buffer, cx)
586                {
587                    this.rename_buffer(old_path, new_path, &buffer, cx)
588                }
589            }
590            _ => {}
591        });
592
593        let on_release = cx.observe_release(buffer_handle, |this, buffer, _| {
594            this.watched_buffers.remove(&buffer.remote_id());
595            this.buffer_marks.remove(&buffer.remote_id());
596        });
597
598        self.watched_buffers.insert(
599            buffer_handle.read(cx).remote_id(),
600            (mark_location, on_change, on_release),
601        );
602    }
603
604    pub fn set_mark(
605        &mut self,
606        name: String,
607        multibuffer: &Entity<MultiBuffer>,
608        anchors: Vec<Anchor>,
609        cx: &mut Context<Self>,
610    ) {
611        let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx);
612        let buffer = multibuffer.read(cx).as_singleton();
613        let abs_path = buffer.as_ref().and_then(|b| self.path_for_buffer(b, cx));
614
615        if self.is_global_mark(&name) && self.global_marks.contains_key(&name) {
616            self.delete_mark(name.clone(), multibuffer, cx);
617        }
618
619        let Some(abs_path) = abs_path else {
620            self.multibuffer_marks
621                .entry(multibuffer.entity_id())
622                .or_default()
623                .insert(name.clone(), anchors);
624            if self.is_global_mark(&name) {
625                self.global_marks
626                    .insert(name, MarkLocation::Buffer(multibuffer.entity_id()));
627            }
628            if let Some(buffer) = buffer {
629                let buffer_id = buffer.read(cx).remote_id();
630                if !self.watched_buffers.contains_key(&buffer_id) {
631                    self.watch_buffer(MarkLocation::Buffer(multibuffer.entity_id()), &buffer, cx)
632                }
633            }
634            return;
635        };
636        let Some(buffer) = buffer else {
637            return;
638        };
639
640        let buffer_id = buffer.read(cx).remote_id();
641        self.buffer_marks.entry(buffer_id).or_default().insert(
642            name.clone(),
643            anchors
644                .into_iter()
645                .filter_map(|anchor| Some(multibuffer_snapshot.anchor_to_buffer_anchor(anchor)?.0))
646                .collect(),
647        );
648        if !self.watched_buffers.contains_key(&buffer_id) {
649            self.watch_buffer(MarkLocation::Path(abs_path.clone()), &buffer, cx)
650        }
651        if self.is_global_mark(&name) {
652            self.global_marks
653                .insert(name, MarkLocation::Path(abs_path.clone()));
654        }
655        self.serialize_buffer_marks(abs_path, &buffer, cx)
656    }
657
658    pub fn get_mark(
659        &self,
660        name: &str,
661        multi_buffer: &Entity<MultiBuffer>,
662        cx: &App,
663    ) -> Option<Mark> {
664        let target = self.global_marks.get(name);
665
666        if !self.is_global_mark(name) || target.is_some_and(|t| self.points_at(t, multi_buffer, cx))
667        {
668            if let Some(anchors) = self.multibuffer_marks.get(&multi_buffer.entity_id()) {
669                return Some(Mark::Local(anchors.get(name)?.clone()));
670            }
671
672            let multibuffer_snapshot = multi_buffer.read(cx).snapshot(cx);
673            let buffer_snapshot = multibuffer_snapshot.as_singleton()?;
674            if let Some(anchors) = self.buffer_marks.get(&buffer_snapshot.remote_id()) {
675                let text_anchors = anchors.get(name)?;
676                let anchors = text_anchors
677                    .iter()
678                    .filter_map(|anchor| multibuffer_snapshot.anchor_in_excerpt(*anchor))
679                    .collect();
680                return Some(Mark::Local(anchors));
681            }
682        }
683
684        match target? {
685            MarkLocation::Buffer(entity_id) => {
686                let anchors = self.multibuffer_marks.get(entity_id)?;
687                Some(Mark::Buffer(*entity_id, anchors.get(name)?.clone()))
688            }
689            MarkLocation::Path(path) => {
690                let points = self.serialized_marks.get(path)?;
691                Some(Mark::Path(path.clone(), points.get(name)?.clone()))
692            }
693        }
694    }
695    pub fn delete_mark(
696        &mut self,
697        mark_name: String,
698        multi_buffer: &Entity<MultiBuffer>,
699        cx: &mut Context<Self>,
700    ) {
701        let path = if let Some(target) = self.global_marks.get(&mark_name.clone()) {
702            let name = mark_name.clone();
703            if let Some(workspace_id) = self.workspace_id(cx) {
704                let db = VimDb::global(cx);
705                cx.background_spawn(async move {
706                    db.delete_global_marks_path(workspace_id, name).await
707                })
708                .detach_and_log_err(cx);
709            }
710            self.buffer_marks.iter_mut().for_each(|(_, m)| {
711                m.remove(&mark_name.clone());
712            });
713
714            match target {
715                MarkLocation::Buffer(entity_id) => {
716                    self.multibuffer_marks
717                        .get_mut(entity_id)
718                        .map(|m| m.remove(&mark_name.clone()));
719                    return;
720                }
721                MarkLocation::Path(path) => path.clone(),
722            }
723        } else {
724            self.multibuffer_marks
725                .get_mut(&multi_buffer.entity_id())
726                .map(|m| m.remove(&mark_name.clone()));
727
728            if let Some(singleton) = multi_buffer.read(cx).as_singleton() {
729                let buffer_id = singleton.read(cx).remote_id();
730                self.buffer_marks
731                    .get_mut(&buffer_id)
732                    .map(|m| m.remove(&mark_name.clone()));
733                let Some(path) = self.path_for_buffer(&singleton, cx) else {
734                    return;
735                };
736                path
737            } else {
738                return;
739            }
740        };
741        self.global_marks.remove(&mark_name);
742        self.serialized_marks
743            .get_mut(&path)
744            .map(|m| m.remove(&mark_name.clone()));
745        if let Some(workspace_id) = self.workspace_id(cx) {
746            let db = VimDb::global(cx);
747            cx.background_spawn(async move { db.delete_mark(workspace_id, path, mark_name).await })
748                .detach_and_log_err(cx);
749        }
750    }
751}
752
753impl Global for VimGlobals {}
754
755impl VimGlobals {
756    pub(crate) fn register(cx: &mut App) {
757        cx.set_global(VimGlobals::default());
758
759        cx.observe_keystrokes(|event, _, cx| {
760            let Some(action) = event.action.as_ref().map(|action| action.boxed_clone()) else {
761                return;
762            };
763            Vim::globals(cx).observe_action(action.boxed_clone())
764        })
765        .detach();
766
767        cx.observe_new(|workspace: &mut Workspace, window, _| {
768            RegistersView::register(workspace, window);
769        })
770        .detach();
771
772        cx.observe_new(move |workspace: &mut Workspace, window, _| {
773            MarksView::register(workspace, window);
774        })
775        .detach();
776
777        let mut was_enabled = None;
778
779        cx.observe_global::<SettingsStore>(move |cx| {
780            let is_enabled = Vim::enabled(cx);
781            if was_enabled == Some(is_enabled) {
782                return;
783            }
784            was_enabled = Some(is_enabled);
785            if is_enabled {
786                KeyBinding::set_vim_mode(cx, true);
787                CommandPaletteFilter::update_global(cx, |filter, _| {
788                    filter.show_namespace(Vim::NAMESPACE);
789                });
790                GlobalCommandPaletteInterceptor::set(cx, command_interceptor);
791                for window in cx.windows() {
792                    if let Some(multi_workspace) = window.downcast::<MultiWorkspace>() {
793                        multi_workspace
794                            .update(cx, |multi_workspace, _, cx| {
795                                for workspace in multi_workspace.workspaces() {
796                                    workspace.update(cx, |workspace, cx| {
797                                        Vim::update_globals(cx, |globals, cx| {
798                                            globals.register_workspace(workspace, cx)
799                                        });
800                                    });
801                                }
802                            })
803                            .ok();
804                    }
805                }
806            } else {
807                KeyBinding::set_vim_mode(cx, false);
808                *Vim::globals(cx) = VimGlobals::default();
809                GlobalCommandPaletteInterceptor::clear(cx);
810                CommandPaletteFilter::update_global(cx, |filter, _| {
811                    filter.hide_namespace(Vim::NAMESPACE);
812                });
813            }
814        })
815        .detach();
816        cx.observe_new(|workspace: &mut Workspace, _, cx| {
817            Vim::update_globals(cx, |globals, cx| globals.register_workspace(workspace, cx));
818        })
819        .detach()
820    }
821
822    fn register_workspace(&mut self, workspace: &Workspace, cx: &mut Context<Workspace>) {
823        let entity_id = cx.entity_id();
824        self.marks.insert(entity_id, MarksState::new(workspace, cx));
825        cx.observe_release(&cx.entity(), move |_, _, cx| {
826            Vim::update_globals(cx, |globals, _| {
827                globals.marks.remove(&entity_id);
828            })
829        })
830        .detach();
831    }
832
833    pub(crate) fn write_registers(
834        &mut self,
835        content: Register,
836        register: Option<char>,
837        is_yank: bool,
838        kind: MotionKind,
839        cx: &mut Context<Editor>,
840    ) {
841        if let Some(register) = register {
842            let lower = register.to_lowercase().next().unwrap_or(register);
843            if lower != register {
844                let current = self.registers.entry(lower).or_default();
845                current.text = (current.text.to_string() + &content.text).into();
846                // not clear how to support appending to registers with multiple cursors
847                current.clipboard_selections.take();
848                let yanked = current.clone();
849                self.registers.insert('"', yanked);
850            } else {
851                match lower {
852                    '_' | ':' | '.' | '%' | '#' | '=' | '/' => {}
853                    '+' => {
854                        self.registers.insert('"', content.clone());
855                        cx.write_to_clipboard(content.into());
856                    }
857                    '*' => {
858                        self.registers.insert('"', content.clone());
859                        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
860                        cx.write_to_primary(content.into());
861                        #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
862                        cx.write_to_clipboard(content.into());
863                    }
864                    '"' => {
865                        self.registers.insert('"', content.clone());
866                        self.registers.insert('0', content);
867                    }
868                    _ => {
869                        self.registers.insert('"', content.clone());
870                        self.registers.insert(lower, content);
871                    }
872                }
873            }
874        } else {
875            let setting = VimSettings::get_global(cx).use_system_clipboard;
876            if setting == UseSystemClipboard::Always
877                || setting == UseSystemClipboard::OnYank && is_yank
878            {
879                self.last_yank.replace(content.text.clone());
880                cx.write_to_clipboard(content.clone().into());
881            } else {
882                if let Some(text) = cx.read_from_clipboard().and_then(|i| i.text()) {
883                    self.last_yank.replace(text.into());
884                }
885            }
886            self.registers.insert('"', content.clone());
887            if is_yank {
888                self.registers.insert('0', content);
889            } else {
890                let contains_newline = content.text.contains('\n');
891                if !contains_newline {
892                    self.registers.insert('-', content.clone());
893                }
894                if kind.linewise() || contains_newline {
895                    let mut content = content;
896                    for i in '1'..='9' {
897                        if let Some(moved) = self.registers.insert(i, content) {
898                            content = moved;
899                        } else {
900                            break;
901                        }
902                    }
903                }
904            }
905        }
906    }
907
908    pub(crate) fn read_register(
909        &self,
910        register: Option<char>,
911        editor: Option<&mut Editor>,
912        cx: &mut App,
913    ) -> Option<Register> {
914        let Some(register) = register.filter(|reg| *reg != '"') else {
915            let setting = VimSettings::get_global(cx).use_system_clipboard;
916            return match setting {
917                UseSystemClipboard::Always => cx.read_from_clipboard().map(|item| item.into()),
918                UseSystemClipboard::OnYank if self.system_clipboard_is_newer(cx) => {
919                    cx.read_from_clipboard().map(|item| item.into())
920                }
921                _ => self.registers.get(&'"').cloned(),
922            };
923        };
924        let lower = register.to_lowercase().next().unwrap_or(register);
925        match lower {
926            '_' | ':' | '.' | '#' | '=' => None,
927            '+' => cx.read_from_clipboard().map(|item| item.into()),
928            '*' => {
929                #[cfg(any(target_os = "linux", target_os = "freebsd"))]
930                {
931                    cx.read_from_primary().map(|item| item.into())
932                }
933                #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
934                {
935                    cx.read_from_clipboard().map(|item| item.into())
936                }
937            }
938            '%' => editor.and_then(|editor| {
939                let multibuffer = editor.buffer().read(cx);
940                let snapshot = multibuffer.snapshot(cx);
941                let selection = editor.selections.newest_anchor();
942                let buffer = snapshot
943                    .anchor_to_buffer_anchor(selection.head())
944                    .and_then(|(text_anchor, _)| multibuffer.buffer(text_anchor.buffer_id));
945                if let Some(buffer) = buffer {
946                    buffer
947                        .read(cx)
948                        .file()
949                        .map(|file| file.path().display(file.path_style(cx)).into_owned().into())
950                } else {
951                    None
952                }
953            }),
954            _ => self.registers.get(&lower).cloned(),
955        }
956    }
957
958    fn system_clipboard_is_newer(&self, cx: &App) -> bool {
959        cx.read_from_clipboard().is_some_and(|item| {
960            match (item.text().as_deref(), &self.last_yank) {
961                (Some(new), Some(last)) => last.as_ref() != new,
962                (Some(_), None) => true,
963                (None, _) => false,
964            }
965        })
966    }
967
968    pub fn observe_action(&mut self, action: Box<dyn Action>) {
969        if self.dot_recording {
970            self.recording_actions
971                .push(ReplayableAction::Action(action.boxed_clone()));
972
973            if self.stop_recording_after_next_action {
974                self.dot_recording = false;
975                self.recorded_actions = std::mem::take(&mut self.recording_actions);
976                self.recorded_count = self.recording_count.take();
977                self.recorded_register_for_dot = self.recording_register_for_dot.take();
978                self.stop_recording_after_next_action = false;
979            }
980        }
981        if self.replayer.is_none()
982            && let Some(recording_register) = self.recording_register
983        {
984            self.recordings
985                .entry(recording_register)
986                .or_default()
987                .push(ReplayableAction::Action(action));
988        }
989    }
990
991    pub fn observe_insertion(&mut self, text: &Arc<str>, range_to_replace: Option<Range<isize>>) {
992        if self.ignore_current_insertion {
993            self.ignore_current_insertion = false;
994            return;
995        }
996        if self.dot_recording {
997            self.recording_actions.push(ReplayableAction::Insertion {
998                text: text.clone(),
999                utf16_range_to_replace: range_to_replace.clone(),
1000            });
1001            if self.stop_recording_after_next_action {
1002                self.dot_recording = false;
1003                self.recorded_actions = std::mem::take(&mut self.recording_actions);
1004                self.recorded_count = self.recording_count.take();
1005                self.recorded_register_for_dot = self.recording_register_for_dot.take();
1006                self.stop_recording_after_next_action = false;
1007            }
1008        }
1009        if let Some(recording_register) = self.recording_register {
1010            self.recordings.entry(recording_register).or_default().push(
1011                ReplayableAction::Insertion {
1012                    text: text.clone(),
1013                    utf16_range_to_replace: range_to_replace,
1014                },
1015            );
1016        }
1017    }
1018
1019    pub fn focused_vim(&self) -> Option<Entity<Vim>> {
1020        self.focused_vim.as_ref().and_then(|vim| vim.upgrade())
1021    }
1022}
1023
1024impl Vim {
1025    pub fn globals(cx: &mut App) -> &mut VimGlobals {
1026        cx.global_mut::<VimGlobals>()
1027    }
1028
1029    pub fn update_globals<C, R>(cx: &mut C, f: impl FnOnce(&mut VimGlobals, &mut C) -> R) -> R
1030    where
1031        C: BorrowMut<App>,
1032    {
1033        cx.update_global(f)
1034    }
1035}
1036
1037#[derive(Debug)]
1038pub enum ReplayableAction {
1039    Action(Box<dyn Action>),
1040    Insertion {
1041        text: Arc<str>,
1042        utf16_range_to_replace: Option<Range<isize>>,
1043    },
1044}
1045
1046impl Clone for ReplayableAction {
1047    fn clone(&self) -> Self {
1048        match self {
1049            Self::Action(action) => Self::Action(action.boxed_clone()),
1050            Self::Insertion {
1051                text,
1052                utf16_range_to_replace,
1053            } => Self::Insertion {
1054                text: text.clone(),
1055                utf16_range_to_replace: utf16_range_to_replace.clone(),
1056            },
1057        }
1058    }
1059}
1060
1061#[derive(Default, Debug)]
1062pub struct SearchState {
1063    pub direction: Direction,
1064    pub count: usize,
1065    pub cmd_f_search: bool,
1066
1067    pub prior_selections: Vec<Range<Anchor>>,
1068    pub prior_operator: Option<Operator>,
1069    pub prior_mode: Mode,
1070    pub helix_select: bool,
1071    pub _dismiss_subscription: Option<gpui::Subscription>,
1072}
1073
1074impl Operator {
1075    pub fn id(&self) -> &'static str {
1076        match self {
1077            Operator::Object { around: false } => "i",
1078            Operator::Object { around: true } => "a",
1079            Operator::Change => "c",
1080            Operator::Delete => "d",
1081            Operator::Yank => "y",
1082            Operator::Replace => "r",
1083            Operator::Digraph { .. } => "^K",
1084            Operator::Literal { .. } => "^V",
1085            Operator::FindForward { before: false, .. } => "f",
1086            Operator::FindForward { before: true, .. } => "t",
1087            Operator::Sneak { .. } => "s",
1088            Operator::SneakBackward { .. } => "S",
1089            Operator::FindBackward { after: false, .. } => "F",
1090            Operator::FindBackward { after: true, .. } => "T",
1091            Operator::AddSurrounds { .. } => "ys",
1092            Operator::ChangeSurrounds { .. } => "cs",
1093            Operator::DeleteSurrounds => "ds",
1094            Operator::Mark => "m",
1095            Operator::Jump { line: true } => "'",
1096            Operator::Jump { line: false } => "`",
1097            Operator::Indent => ">",
1098            Operator::AutoIndent => "eq",
1099            Operator::ShellCommand => "sh",
1100            Operator::Rewrap => "gq",
1101            Operator::ReplaceWithRegister => "gR",
1102            Operator::Exchange => "cx",
1103            Operator::Outdent => "<",
1104            Operator::Uppercase => "gU",
1105            Operator::Lowercase => "gu",
1106            Operator::OppositeCase => "g~",
1107            Operator::Rot13 => "g?",
1108            Operator::Rot47 => "g?",
1109            Operator::Register => "\"",
1110            Operator::RecordRegister => "q",
1111            Operator::ReplayRegister => "@",
1112            Operator::ToggleComments => "gc",
1113            Operator::ToggleBlockComments => "gb",
1114            Operator::HelixMatch => "helix_m",
1115            Operator::HelixNext { .. } => "helix_next",
1116            Operator::HelixPrevious { .. } => "helix_previous",
1117            Operator::HelixJump { .. } => "gw",
1118            Operator::HelixSurroundAdd => "helix_ms",
1119            Operator::HelixSurroundReplace { .. } => "helix_mr",
1120            Operator::HelixSurroundDelete => "helix_md",
1121        }
1122    }
1123
1124    pub fn status(&self) -> String {
1125        fn make_visible(c: &str) -> &str {
1126            match c {
1127                "\n" => "enter",
1128                "\t" => "tab",
1129                " " => "space",
1130                c => c,
1131            }
1132        }
1133        match self {
1134            Operator::Digraph {
1135                first_char: Some(first_char),
1136            } => format!("^K{}", make_visible(&first_char.to_string())),
1137            Operator::Literal {
1138                prefix: Some(prefix),
1139            } => format!("^V{}", make_visible(prefix)),
1140            Operator::AutoIndent => "=".to_string(),
1141            Operator::ShellCommand => "=".to_string(),
1142            Operator::HelixMatch => "m".to_string(),
1143            Operator::HelixNext { .. } => "]".to_string(),
1144            Operator::HelixPrevious { .. } => "[".to_string(),
1145            Operator::HelixJump { .. } => "gw".to_string(),
1146            Operator::HelixSurroundAdd => "ms".to_string(),
1147            Operator::HelixSurroundReplace {
1148                replaced_char: None,
1149            } => "mr".to_string(),
1150            Operator::HelixSurroundReplace {
1151                replaced_char: Some(c),
1152            } => format!("mr{}", c),
1153            Operator::HelixSurroundDelete => "md".to_string(),
1154            _ => self.id().to_string(),
1155        }
1156    }
1157
1158    pub fn is_waiting(&self, mode: Mode) -> bool {
1159        match self {
1160            Operator::AddSurrounds { target } => target.is_some() || mode.is_visual(),
1161            Operator::FindForward { .. }
1162            | Operator::Mark
1163            | Operator::Jump { .. }
1164            | Operator::FindBackward { .. }
1165            | Operator::Sneak { .. }
1166            | Operator::SneakBackward { .. }
1167            | Operator::Register
1168            | Operator::RecordRegister
1169            | Operator::ReplayRegister
1170            | Operator::Replace
1171            | Operator::Digraph { .. }
1172            | Operator::Literal { .. }
1173            | Operator::ChangeSurrounds {
1174                target: Some(_), ..
1175            }
1176            | Operator::DeleteSurrounds
1177            | Operator::HelixJump { .. } => true,
1178            Operator::Change
1179            | Operator::Delete
1180            | Operator::Yank
1181            | Operator::Rewrap
1182            | Operator::Indent
1183            | Operator::Outdent
1184            | Operator::AutoIndent
1185            | Operator::ShellCommand
1186            | Operator::Lowercase
1187            | Operator::Uppercase
1188            | Operator::Rot13
1189            | Operator::Rot47
1190            | Operator::ReplaceWithRegister
1191            | Operator::Exchange
1192            | Operator::Object { .. }
1193            | Operator::ChangeSurrounds { target: None, .. }
1194            | Operator::OppositeCase
1195            | Operator::ToggleComments
1196            | Operator::ToggleBlockComments
1197            | Operator::HelixMatch
1198            | Operator::HelixNext { .. }
1199            | Operator::HelixPrevious { .. } => false,
1200            Operator::HelixSurroundAdd
1201            | Operator::HelixSurroundReplace { .. }
1202            | Operator::HelixSurroundDelete => true,
1203        }
1204    }
1205
1206    pub fn starts_dot_recording(&self) -> bool {
1207        match self {
1208            Operator::Change
1209            | Operator::Delete
1210            | Operator::Replace
1211            | Operator::Indent
1212            | Operator::Outdent
1213            | Operator::AutoIndent
1214            | Operator::Lowercase
1215            | Operator::Uppercase
1216            | Operator::OppositeCase
1217            | Operator::Rot13
1218            | Operator::Rot47
1219            | Operator::ToggleComments
1220            | Operator::ToggleBlockComments
1221            | Operator::ReplaceWithRegister
1222            | Operator::Rewrap
1223            | Operator::ShellCommand
1224            | Operator::AddSurrounds { target: None }
1225            | Operator::ChangeSurrounds { target: None, .. }
1226            | Operator::DeleteSurrounds
1227            | Operator::Exchange
1228            | Operator::HelixNext { .. }
1229            | Operator::HelixPrevious { .. }
1230            | Operator::HelixSurroundAdd
1231            | Operator::HelixSurroundReplace { .. }
1232            | Operator::HelixSurroundDelete => true,
1233            Operator::Yank
1234            | Operator::Object { .. }
1235            | Operator::FindForward { .. }
1236            | Operator::FindBackward { .. }
1237            | Operator::Sneak { .. }
1238            | Operator::SneakBackward { .. }
1239            | Operator::Mark
1240            | Operator::Digraph { .. }
1241            | Operator::Literal { .. }
1242            | Operator::AddSurrounds { .. }
1243            | Operator::ChangeSurrounds { .. }
1244            | Operator::Jump { .. }
1245            | Operator::Register
1246            | Operator::RecordRegister
1247            | Operator::ReplayRegister
1248            | Operator::HelixMatch
1249            | Operator::HelixJump { .. } => false,
1250        }
1251    }
1252}
1253
1254struct RegisterMatch {
1255    name: char,
1256    contents: SharedString,
1257}
1258
1259pub struct RegistersViewDelegate {
1260    selected_index: usize,
1261    matches: Vec<RegisterMatch>,
1262}
1263
1264impl PickerDelegate for RegistersViewDelegate {
1265    type ListItem = Div;
1266
1267    fn name() -> &'static str {
1268        "registers view"
1269    }
1270
1271    fn match_count(&self) -> usize {
1272        self.matches.len()
1273    }
1274
1275    fn selected_index(&self) -> usize {
1276        self.selected_index
1277    }
1278
1279    fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
1280        self.selected_index = ix;
1281        cx.notify();
1282    }
1283
1284    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
1285        Arc::default()
1286    }
1287
1288    fn update_matches(
1289        &mut self,
1290        _: String,
1291        _: &mut Window,
1292        _: &mut Context<Picker<Self>>,
1293    ) -> gpui::Task<()> {
1294        Task::ready(())
1295    }
1296
1297    fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context<Picker<Self>>) {}
1298
1299    fn dismissed(&mut self, _: &mut Window, _: &mut Context<Picker<Self>>) {}
1300
1301    fn render_match(
1302        &self,
1303        ix: usize,
1304        selected: bool,
1305        _: &mut Window,
1306        cx: &mut Context<Picker<Self>>,
1307    ) -> Option<Self::ListItem> {
1308        let register_match = self.matches.get(ix)?;
1309
1310        let mut output = String::new();
1311        let mut runs = Vec::new();
1312        output.push('"');
1313        output.push(register_match.name);
1314        runs.push((
1315            0..output.len(),
1316            HighlightStyle::color(cx.theme().colors().text_accent),
1317        ));
1318        output.push(' ');
1319        output.push(' ');
1320        let mut base = output.len();
1321        for (ix, c) in register_match.contents.char_indices() {
1322            if ix > 100 {
1323                break;
1324            }
1325            let replace = match c {
1326                '\t' => Some("\\t".to_string()),
1327                '\n' => Some("\\n".to_string()),
1328                '\r' => Some("\\r".to_string()),
1329                c if is_invisible(c) => {
1330                    if c <= '\x1f' {
1331                        replacement(c).map(|s| s.to_string())
1332                    } else {
1333                        Some(format!("\\u{:04X}", c as u32))
1334                    }
1335                }
1336                _ => None,
1337            };
1338            let Some(replace) = replace else {
1339                output.push(c);
1340                continue;
1341            };
1342            output.push_str(&replace);
1343            runs.push((
1344                base + ix..base + ix + replace.len(),
1345                HighlightStyle::color(cx.theme().colors().text_muted),
1346            ));
1347            base += replace.len() - c.len_utf8();
1348        }
1349
1350        let theme = ThemeSettings::get_global(cx);
1351        let text_style = TextStyle {
1352            color: cx.theme().colors().editor_foreground,
1353            font_family: theme.buffer_font.family.clone(),
1354            font_features: theme.buffer_font.features.clone(),
1355            font_fallbacks: theme.buffer_font.fallbacks.clone(),
1356            font_size: theme.buffer_font_size(cx).into(),
1357            line_height: (theme.line_height() * theme.buffer_font_size(cx)).into(),
1358            font_weight: theme.buffer_font.weight,
1359            font_style: theme.buffer_font.style,
1360            ..Default::default()
1361        };
1362
1363        Some(
1364            h_flex()
1365                .when(selected, |el| el.bg(cx.theme().colors().element_selected))
1366                .font_buffer(cx)
1367                .text_buffer(cx)
1368                .h(theme.buffer_font_size(cx) * theme.line_height())
1369                .px_2()
1370                .gap_1()
1371                .child(StyledText::new(output).with_default_highlights(&text_style, runs)),
1372        )
1373    }
1374}
1375
1376pub struct RegistersView {}
1377
1378impl RegistersView {
1379    fn register(workspace: &mut Workspace, _window: Option<&mut Window>) {
1380        workspace.register_action(|workspace, _: &ToggleRegistersView, window, cx| {
1381            Self::toggle(workspace, window, cx);
1382        });
1383    }
1384
1385    pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
1386        let editor = workspace
1387            .active_item(cx)
1388            .and_then(|item| item.act_as::<Editor>(cx));
1389        workspace.toggle_modal(window, cx, move |window, cx| {
1390            RegistersView::new(editor, window, cx)
1391        });
1392    }
1393
1394    fn new(
1395        editor: Option<Entity<Editor>>,
1396        window: &mut Window,
1397        cx: &mut Context<Picker<RegistersViewDelegate>>,
1398    ) -> Picker<RegistersViewDelegate> {
1399        let mut matches = Vec::default();
1400        cx.update_global(|globals: &mut VimGlobals, cx| {
1401            for name in ['"', '+', '*'] {
1402                if let Some(register) = globals.read_register(Some(name), None, cx) {
1403                    matches.push(RegisterMatch {
1404                        name,
1405                        contents: register.text.clone(),
1406                    })
1407                }
1408            }
1409            if let Some(editor) = editor {
1410                let register = editor.update(cx, |editor, cx| {
1411                    globals.read_register(Some('%'), Some(editor), cx)
1412                });
1413                if let Some(register) = register {
1414                    matches.push(RegisterMatch {
1415                        name: '%',
1416                        contents: register.text,
1417                    })
1418                }
1419            }
1420            for (name, register) in globals.registers.iter() {
1421                if ['"', '+', '*', '%'].contains(name) {
1422                    continue;
1423                };
1424                matches.push(RegisterMatch {
1425                    name: *name,
1426                    contents: register.text.clone(),
1427                })
1428            }
1429        });
1430        matches.sort_by_key(|m| m.name);
1431        let delegate = RegistersViewDelegate {
1432            selected_index: 0,
1433            matches,
1434        };
1435
1436        Picker::nonsearchable_uniform_list(delegate, window, cx).initial_width(rems(36.))
1437    }
1438}
1439
1440enum MarksMatchInfo {
1441    Path(Arc<Path>),
1442    Title(String),
1443    Content {
1444        line: String,
1445        highlights: Vec<(Range<usize>, HighlightStyle)>,
1446    },
1447}
1448
1449impl MarksMatchInfo {
1450    fn from_chunks<'a>(chunks: impl Iterator<Item = Chunk<'a>>, cx: &App) -> Self {
1451        let mut line = String::new();
1452        let mut highlights = Vec::new();
1453        let mut offset = 0;
1454        for chunk in chunks {
1455            line.push_str(chunk.text);
1456            if let Some(highlight_id) = chunk.syntax_highlight_id
1457                && let Some(highlight) = cx.theme().syntax().get(highlight_id).cloned()
1458            {
1459                highlights.push((offset..offset + chunk.text.len(), highlight))
1460            }
1461            offset += chunk.text.len();
1462        }
1463        MarksMatchInfo::Content { line, highlights }
1464    }
1465}
1466
1467struct MarksMatch {
1468    name: String,
1469    position: Point,
1470    info: MarksMatchInfo,
1471}
1472
1473pub struct MarksViewDelegate {
1474    selected_index: usize,
1475    matches: Vec<MarksMatch>,
1476    point_column_width: usize,
1477    workspace: WeakEntity<Workspace>,
1478}
1479
1480impl PickerDelegate for MarksViewDelegate {
1481    type ListItem = Div;
1482
1483    fn name() -> &'static str {
1484        "marks view"
1485    }
1486
1487    fn match_count(&self) -> usize {
1488        self.matches.len()
1489    }
1490
1491    fn selected_index(&self) -> usize {
1492        self.selected_index
1493    }
1494
1495    fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
1496        self.selected_index = ix;
1497        cx.notify();
1498    }
1499
1500    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
1501        Arc::default()
1502    }
1503
1504    fn update_matches(
1505        &mut self,
1506        _: String,
1507        _: &mut Window,
1508        cx: &mut Context<Picker<Self>>,
1509    ) -> gpui::Task<()> {
1510        let Some(workspace) = self.workspace.upgrade() else {
1511            return Task::ready(());
1512        };
1513        cx.spawn(async move |picker, cx| {
1514            let mut matches = Vec::new();
1515            let _ = workspace.update(cx, |workspace, cx| {
1516                let entity_id = cx.entity_id();
1517                let Some(editor) = workspace
1518                    .active_item(cx)
1519                    .and_then(|item| item.act_as::<Editor>(cx))
1520                else {
1521                    return;
1522                };
1523                let editor = editor.read(cx);
1524                let mut has_seen = HashSet::new();
1525                let Some(marks_state) = cx.global::<VimGlobals>().marks.get(&entity_id) else {
1526                    return;
1527                };
1528                let marks_state = marks_state.read(cx);
1529
1530                if let Some(map) = marks_state
1531                    .multibuffer_marks
1532                    .get(&editor.buffer().entity_id())
1533                {
1534                    for (name, anchors) in map {
1535                        if has_seen.contains(name) {
1536                            continue;
1537                        }
1538                        has_seen.insert(name.clone());
1539                        let Some(anchor) = anchors.first() else {
1540                            continue;
1541                        };
1542
1543                        let snapshot = editor.buffer().read(cx).snapshot(cx);
1544                        let position = anchor.to_point(&snapshot);
1545
1546                        let chunks = snapshot.chunks(
1547                            Point::new(position.row, 0)
1548                                ..Point::new(
1549                                    position.row,
1550                                    snapshot.line_len(MultiBufferRow(position.row)),
1551                                ),
1552                            LanguageAwareStyling {
1553                                tree_sitter: true,
1554                                diagnostics: true,
1555                            },
1556                        );
1557                        matches.push(MarksMatch {
1558                            name: name.clone(),
1559                            position,
1560                            info: MarksMatchInfo::from_chunks(chunks, cx),
1561                        })
1562                    }
1563                }
1564
1565                if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
1566                    let buffer = buffer.read(cx);
1567                    if let Some(map) = marks_state.buffer_marks.get(&buffer.remote_id()) {
1568                        for (name, anchors) in map {
1569                            if has_seen.contains(name) {
1570                                continue;
1571                            }
1572                            has_seen.insert(name.clone());
1573                            let Some(anchor) = anchors.first() else {
1574                                continue;
1575                            };
1576                            let snapshot = buffer.snapshot();
1577                            let position = anchor.to_point(&snapshot);
1578                            let chunks = snapshot.chunks(
1579                                Point::new(position.row, 0)
1580                                    ..Point::new(position.row, snapshot.line_len(position.row)),
1581                                LanguageAwareStyling {
1582                                    tree_sitter: true,
1583                                    diagnostics: true,
1584                                },
1585                            );
1586
1587                            matches.push(MarksMatch {
1588                                name: name.clone(),
1589                                position,
1590                                info: MarksMatchInfo::from_chunks(chunks, cx),
1591                            })
1592                        }
1593                    }
1594                }
1595
1596                for (name, mark_location) in marks_state.global_marks.iter() {
1597                    if has_seen.contains(name) {
1598                        continue;
1599                    }
1600                    has_seen.insert(name.clone());
1601
1602                    match mark_location {
1603                        MarkLocation::Buffer(entity_id) => {
1604                            if let Some(&anchor) = marks_state
1605                                .multibuffer_marks
1606                                .get(entity_id)
1607                                .and_then(|map| map.get(name))
1608                                .and_then(|anchors| anchors.first())
1609                            {
1610                                let Some((info, snapshot)) = workspace
1611                                    .items(cx)
1612                                    .filter_map(|item| item.act_as::<Editor>(cx))
1613                                    .map(|entity| entity.read(cx).buffer())
1614                                    .find(|buffer| buffer.entity_id().eq(entity_id))
1615                                    .map(|buffer| {
1616                                        (
1617                                            MarksMatchInfo::Title(
1618                                                buffer.read(cx).title(cx).to_string(),
1619                                            ),
1620                                            buffer.read(cx).snapshot(cx),
1621                                        )
1622                                    })
1623                                else {
1624                                    continue;
1625                                };
1626                                matches.push(MarksMatch {
1627                                    name: name.clone(),
1628                                    position: anchor.to_point(&snapshot),
1629                                    info,
1630                                });
1631                            }
1632                        }
1633                        MarkLocation::Path(path) => {
1634                            if let Some(&position) = marks_state
1635                                .serialized_marks
1636                                .get(path.as_ref())
1637                                .and_then(|map| map.get(name))
1638                                .and_then(|points| points.first())
1639                            {
1640                                let info = MarksMatchInfo::Path(path.clone());
1641                                matches.push(MarksMatch {
1642                                    name: name.clone(),
1643                                    position,
1644                                    info,
1645                                });
1646                            }
1647                        }
1648                    }
1649                }
1650            });
1651            let _ = picker.update(cx, |picker, cx| {
1652                matches.sort_by_key(|a| {
1653                    (
1654                        a.name.chars().next().map(|c| c.is_ascii_uppercase()),
1655                        a.name.clone(),
1656                    )
1657                });
1658                let digits = matches
1659                    .iter()
1660                    .map(|m| (m.position.row + 1).ilog10() + (m.position.column + 1).ilog10())
1661                    .max()
1662                    .unwrap_or_default();
1663                picker.delegate.matches = matches;
1664                picker.delegate.point_column_width = (digits + 4) as usize;
1665                cx.notify();
1666            });
1667        })
1668    }
1669
1670    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
1671        let Some(vim) = self
1672            .workspace
1673            .upgrade()
1674            .map(|w| w.read(cx))
1675            .and_then(|w| w.focused_pane(window, cx).read(cx).active_item())
1676            .and_then(|item| item.act_as::<Editor>(cx))
1677            .and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned())
1678            .map(|addon| addon.entity)
1679        else {
1680            return;
1681        };
1682        let Some(text): Option<Arc<str>> = self
1683            .matches
1684            .get(self.selected_index)
1685            .map(|m| Arc::from(m.name.to_string().into_boxed_str()))
1686        else {
1687            return;
1688        };
1689        vim.update(cx, |vim, cx| {
1690            vim.jump(text, false, false, window, cx);
1691        });
1692
1693        cx.emit(DismissEvent);
1694    }
1695
1696    fn dismissed(&mut self, _: &mut Window, _: &mut Context<Picker<Self>>) {}
1697
1698    fn render_match(
1699        &self,
1700        ix: usize,
1701        selected: bool,
1702        _: &mut Window,
1703        cx: &mut Context<Picker<Self>>,
1704    ) -> Option<Self::ListItem> {
1705        let mark_match = self.matches.get(ix)?;
1706
1707        let mut left_output = String::new();
1708        let mut left_runs = Vec::new();
1709        left_output.push('`');
1710        left_output.push_str(&mark_match.name);
1711        left_runs.push((
1712            0..left_output.len(),
1713            HighlightStyle::color(cx.theme().colors().text_accent),
1714        ));
1715        left_output.push(' ');
1716        left_output.push(' ');
1717        let point_column = format!(
1718            "{},{}",
1719            mark_match.position.row + 1,
1720            mark_match.position.column + 1
1721        );
1722        left_output.push_str(&point_column);
1723        if let Some(padding) = self.point_column_width.checked_sub(point_column.len()) {
1724            left_output.push_str(&" ".repeat(padding));
1725        }
1726
1727        let (right_output, right_runs): (String, Vec<_>) = match &mark_match.info {
1728            MarksMatchInfo::Path(path) => {
1729                let s = path.to_string_lossy().into_owned();
1730                (
1731                    s.clone(),
1732                    vec![(0..s.len(), HighlightStyle::color(cx.theme().colors().text))],
1733                )
1734            }
1735            MarksMatchInfo::Title(title) => (
1736                title.clone(),
1737                vec![(
1738                    0..title.len(),
1739                    HighlightStyle::color(cx.theme().colors().text),
1740                )],
1741            ),
1742            MarksMatchInfo::Content { line, highlights } => (line.clone(), highlights.clone()),
1743        };
1744
1745        let theme = ThemeSettings::get_global(cx);
1746        let text_style = TextStyle {
1747            color: cx.theme().colors().editor_foreground,
1748            font_family: theme.buffer_font.family.clone(),
1749            font_features: theme.buffer_font.features.clone(),
1750            font_fallbacks: theme.buffer_font.fallbacks.clone(),
1751            font_size: theme.buffer_font_size(cx).into(),
1752            line_height: (theme.line_height() * theme.buffer_font_size(cx)).into(),
1753            font_weight: theme.buffer_font.weight,
1754            font_style: theme.buffer_font.style,
1755            ..Default::default()
1756        };
1757
1758        Some(
1759            h_flex()
1760                .when(selected, |el| el.bg(cx.theme().colors().element_selected))
1761                .font_buffer(cx)
1762                .text_buffer(cx)
1763                .h(theme.buffer_font_size(cx) * theme.line_height())
1764                .px_2()
1765                .child(StyledText::new(left_output).with_default_highlights(&text_style, left_runs))
1766                .child(
1767                    StyledText::new(right_output).with_default_highlights(&text_style, right_runs),
1768                ),
1769        )
1770    }
1771}
1772
1773pub struct MarksView {}
1774
1775impl MarksView {
1776    fn register(workspace: &mut Workspace, _window: Option<&mut Window>) {
1777        workspace.register_action(|workspace, _: &ToggleMarksView, window, cx| {
1778            Self::toggle(workspace, window, cx);
1779        });
1780    }
1781
1782    pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
1783        let handle = cx.weak_entity();
1784        workspace.toggle_modal(window, cx, move |window, cx| {
1785            MarksView::new(handle, window, cx)
1786        });
1787    }
1788
1789    fn new(
1790        workspace: WeakEntity<Workspace>,
1791        window: &mut Window,
1792        cx: &mut Context<Picker<MarksViewDelegate>>,
1793    ) -> Picker<MarksViewDelegate> {
1794        let matches = Vec::default();
1795        let delegate = MarksViewDelegate {
1796            selected_index: 0,
1797            point_column_width: 0,
1798            matches,
1799            workspace,
1800        };
1801        Picker::nonsearchable_uniform_list(delegate, window, cx).initial_width(rems(36.))
1802    }
1803}
1804
1805pub struct VimDb(ThreadSafeConnection);
1806
1807impl Domain for VimDb {
1808    const NAME: &str = stringify!(VimDb);
1809
1810    const MIGRATIONS: &[&str] = &[
1811        sql! (
1812            CREATE TABLE vim_marks (
1813              workspace_id INTEGER,
1814              mark_name TEXT,
1815              path BLOB,
1816              value TEXT
1817            );
1818            CREATE UNIQUE INDEX idx_vim_marks ON vim_marks (workspace_id, mark_name, path);
1819        ),
1820        sql! (
1821            CREATE TABLE vim_global_marks_paths(
1822                workspace_id INTEGER,
1823                mark_name TEXT,
1824                path BLOB
1825            );
1826            CREATE UNIQUE INDEX idx_vim_global_marks_paths
1827            ON vim_global_marks_paths(workspace_id, mark_name);
1828        ),
1829    ];
1830}
1831
1832db::static_connection!(VimDb, [WorkspaceDb]);
1833
1834struct SerializedMark {
1835    path: Arc<Path>,
1836    name: String,
1837    points: Vec<Point>,
1838}
1839
1840impl VimDb {
1841    pub(crate) async fn set_marks(
1842        &self,
1843        workspace_id: WorkspaceId,
1844        path: Arc<Path>,
1845        marks: HashMap<String, Vec<Point>>,
1846    ) -> Result<()> {
1847        log::debug!("Setting path {path:?} for {} marks", marks.len());
1848
1849        self.write(move |conn| {
1850            let mut query = conn.exec_bound(sql!(
1851                INSERT OR REPLACE INTO vim_marks
1852                    (workspace_id, mark_name, path, value)
1853                VALUES
1854                    (?, ?, ?, ?)
1855            ))?;
1856            for (mark_name, value) in marks {
1857                let pairs: Vec<(u32, u32)> = value
1858                    .into_iter()
1859                    .map(|point| (point.row, point.column))
1860                    .collect();
1861                let serialized = serde_json::to_string(&pairs)?;
1862                query((workspace_id, mark_name, path.clone(), serialized))?;
1863            }
1864            Ok(())
1865        })
1866        .await
1867    }
1868
1869    fn get_marks(&self, workspace_id: WorkspaceId) -> Result<Vec<SerializedMark>> {
1870        let result: Vec<(Arc<Path>, String, String)> = self.select_bound(sql!(
1871            SELECT path, mark_name, value FROM vim_marks
1872                WHERE workspace_id = ?
1873        ))?(workspace_id)?;
1874
1875        Ok(result
1876            .into_iter()
1877            .filter_map(|(path, name, value)| {
1878                let pairs: Vec<(u32, u32)> = serde_json::from_str(&value).log_err()?;
1879                Some(SerializedMark {
1880                    path,
1881                    name,
1882                    points: pairs
1883                        .into_iter()
1884                        .map(|(row, column)| Point { row, column })
1885                        .collect(),
1886                })
1887            })
1888            .collect())
1889    }
1890
1891    pub(crate) async fn delete_mark(
1892        &self,
1893        workspace_id: WorkspaceId,
1894        path: Arc<Path>,
1895        mark_name: String,
1896    ) -> Result<()> {
1897        self.write(move |conn| {
1898            conn.exec_bound(sql!(
1899                DELETE FROM vim_marks
1900                WHERE workspace_id = ? AND mark_name = ? AND path = ?
1901            ))?((workspace_id, mark_name, path))
1902        })
1903        .await
1904    }
1905
1906    pub(crate) async fn set_global_mark_path(
1907        &self,
1908        workspace_id: WorkspaceId,
1909        mark_name: String,
1910        path: Arc<Path>,
1911    ) -> Result<()> {
1912        log::debug!("Setting global mark path {path:?} for {mark_name}");
1913        self.write(move |conn| {
1914            conn.exec_bound(sql!(
1915                INSERT OR REPLACE INTO vim_global_marks_paths
1916                    (workspace_id, mark_name, path)
1917                VALUES
1918                    (?, ?, ?)
1919            ))?((workspace_id, mark_name, path))
1920        })
1921        .await
1922    }
1923
1924    pub fn get_global_marks_paths(
1925        &self,
1926        workspace_id: WorkspaceId,
1927    ) -> Result<Vec<(String, Arc<Path>)>> {
1928        self.select_bound(sql!(
1929        SELECT mark_name, path FROM vim_global_marks_paths
1930            WHERE workspace_id = ?
1931        ))?(workspace_id)
1932    }
1933
1934    pub(crate) async fn delete_global_marks_path(
1935        &self,
1936        workspace_id: WorkspaceId,
1937        mark_name: String,
1938    ) -> Result<()> {
1939        self.write(move |conn| {
1940            conn.exec_bound(sql!(
1941                DELETE FROM vim_global_marks_paths
1942                WHERE workspace_id = ? AND mark_name = ?
1943            ))?((workspace_id, mark_name))
1944        })
1945        .await
1946    }
1947}
1948
Served at tenant.openagents/omega Member data and write actions are omitted.