state.rs

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