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