state.rs

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