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