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