state.rs

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