use crate::command::command_interceptor;use crate::motion::MotionKind;use crate::normal::repeat::Replayer;use crate::surrounds::SurroundsType;use crate::{ToggleMarksView, ToggleRegistersView, UseSystemClipboard, Vim, VimAddon, VimSettings};use crate::{motion::Motion, object::Object};use anyhow::Result;use collections::HashMap;use command_palette_hooks::{CommandPaletteFilter, GlobalCommandPaletteInterceptor};use db::{ sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection}, sqlez_macros::sql,};use editor::display_map::{is_invisible, replacement};use editor::{Anchor, ClipboardSelection, Editor, MultiBuffer, ToPoint as EditorToPoint};use gpui::{ Action, App, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, DismissEvent, Entity, EntityId, Global, HighlightStyle, StyledText, Subscription, Task, TextStyle, WeakEntity,};use language::{Buffer, BufferEvent, BufferId, Chunk, Point};use multi_buffer::MultiBufferRow;use picker::{Picker, PickerDelegate};use project::{Project, ProjectItem, ProjectPath};use serde::{Deserialize, Serialize};use settings::{Settings, SettingsStore};use std::borrow::BorrowMut;use std::collections::HashSet;use std::path::Path;use std::{fmt::Display, ops::Range, sync::Arc};use text::{Bias, ToPoint};use theme::ThemeSettings;use ui::{ ActiveTheme, Context, Div, FluentBuilder, KeyBinding, ParentElement, SharedString, Styled, StyledTypography, Window, h_flex, rems,};use util::ResultExt;use util::rel_path::RelPath;use workspace::searchable::Direction;use workspace::{Workspace, WorkspaceDb, WorkspaceId}; #[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)]pub enum Mode { #[default] Normal, Insert, Replace, Visual, VisualLine, VisualBlock, HelixNormal, HelixSelect,} impl Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Mode::Normal => write!(f, "NORMAL"), Mode::Insert => write!(f, "INSERT"), Mode::Replace => write!(f, "REPLACE"), Mode::Visual => write!(f, "VISUAL"), Mode::VisualLine => write!(f, "VISUAL LINE"), Mode::VisualBlock => write!(f, "VISUAL BLOCK"), Mode::HelixNormal => write!(f, "NORMAL"), Mode::HelixSelect => write!(f, "SELECT"), } }} impl Mode { pub fn is_visual(&self) -> bool { match self { Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true, Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false, } }} #[derive(Clone, Debug, PartialEq)]pub enum Operator { Change, Delete, Yank, Replace, Object { around: bool, }, FindForward { before: bool, multiline: bool, }, FindBackward { after: bool, multiline: bool, }, Sneak { first_char: Option<char>, }, SneakBackward { first_char: Option<char>, }, AddSurrounds { // Typically no need to configure this as `SendKeystrokes` can be used - see #23088. target: Option<SurroundsType>, }, ChangeSurrounds { target: Option<Object>, /// Represents whether the opening bracket was used for the target /// object. opening: bool, }, DeleteSurrounds, Mark, Jump { line: bool, }, Indent, Outdent, AutoIndent, Rewrap, ShellCommand, Lowercase, Uppercase, OppositeCase, Rot13, Rot47, Digraph { first_char: Option<char>, }, Literal { prefix: Option<String>, }, Register, RecordRegister, ReplayRegister, ToggleComments, ReplaceWithRegister, Exchange, HelixMatch, HelixNext { around: bool, }, HelixPrevious { around: bool, },} #[derive(Default, Clone, Debug)]pub enum RecordedSelection { #[default] None, Visual { rows: u32, cols: u32, }, SingleLine { cols: u32, }, VisualBlock { rows: u32, cols: u32, }, VisualLine { rows: u32, },} #[derive(Default, Clone, Debug)]pub struct Register { pub(crate) text: SharedString, pub(crate) clipboard_selections: Option<Vec<ClipboardSelection>>,} impl From<Register> for ClipboardItem { fn from(register: Register) -> Self { if let Some(clipboard_selections) = register.clipboard_selections { ClipboardItem::new_string_with_json_metadata(register.text.into(), clipboard_selections) } else { ClipboardItem::new_string(register.text.into()) } }} impl From<ClipboardItem> for Register { fn from(item: ClipboardItem) -> Self { // For now, we don't store metadata for multiple entries. match item.entries().first() { Some(ClipboardEntry::String(value)) if item.entries().len() == 1 => Register { text: value.text().to_owned().into(), clipboard_selections: value.metadata_json::<Vec<ClipboardSelection>>(), }, // For now, registers can't store images. This could change in the future. _ => Register::default(), } }} impl From<String> for Register { fn from(text: String) -> Self { Register { text: text.into(), clipboard_selections: None, } }} #[derive(Default)]pub struct VimGlobals { pub last_find: Option<Motion>, pub dot_recording: bool, pub dot_replaying: bool, /// pre_count is the number before an operator is specified (3 in 3d2d) pub pre_count: Option<usize>, /// post_count is the number after an operator is specified (2 in 3d2d) pub post_count: Option<usize>, pub forced_motion: bool, pub stop_recording_after_next_action: bool, pub ignore_current_insertion: bool, pub recording_count: Option<usize>, pub recorded_count: Option<usize>, pub recording_actions: Vec<ReplayableAction>, pub recorded_actions: Vec<ReplayableAction>, pub recorded_selection: RecordedSelection, pub recording_register: Option<char>, pub last_recorded_register: Option<char>, pub last_replayed_register: Option<char>, pub replayer: Option<Replayer>, pub last_yank: Option<SharedString>, pub registers: HashMap<char, Register>, pub recordings: HashMap<char, Vec<ReplayableAction>>, pub focused_vim: Option<WeakEntity<Vim>>, pub marks: HashMap<EntityId, Entity<MarksState>>,} pub struct MarksState { workspace: WeakEntity<Workspace>, multibuffer_marks: HashMap<EntityId, HashMap<String, Vec<Anchor>>>, buffer_marks: HashMap<BufferId, HashMap<String, Vec<text::Anchor>>>, watched_buffers: HashMap<BufferId, (MarkLocation, Subscription, Subscription)>, serialized_marks: HashMap<Arc<Path>, HashMap<String, Vec<Point>>>, global_marks: HashMap<String, MarkLocation>, _subscription: Subscription,} #[derive(Debug, PartialEq, Eq, Clone)]pub enum MarkLocation { Buffer(EntityId), Path(Arc<Path>),} pub enum Mark { Local(Vec<Anchor>), Buffer(EntityId, Vec<Anchor>), Path(Arc<Path>, Vec<Point>),} impl MarksState { pub fn new(workspace: &Workspace, cx: &mut App) -> Entity<MarksState> { cx.new(|cx| { let buffer_store = workspace.project().read(cx).buffer_store().clone(); let subscription = cx.subscribe(&buffer_store, move |this: &mut Self, _, event, cx| { if let project::buffer_store::BufferStoreEvent::BufferAdded(buffer) = event { this.on_buffer_loaded(buffer, cx); } }); let mut this = Self { workspace: workspace.weak_handle(), multibuffer_marks: HashMap::default(), buffer_marks: HashMap::default(), watched_buffers: HashMap::default(), serialized_marks: HashMap::default(), global_marks: HashMap::default(), _subscription: subscription, }; this.load(cx); this }) } fn workspace_id(&self, cx: &App) -> Option<WorkspaceId> { self.workspace .read_with(cx, |workspace, _| workspace.database_id()) .ok() .flatten() } fn project(&self, cx: &App) -> Option<Entity<Project>> { self.workspace .read_with(cx, |workspace, _| workspace.project().clone()) .ok() } fn load(&mut self, cx: &mut Context<Self>) { cx.spawn(async move |this, cx| { let Some(workspace_id) = this.update(cx, |this, cx| this.workspace_id(cx)).ok()? else { return None; }; let (marks, paths) = cx .background_spawn(async move { let marks = DB.get_marks(workspace_id)?; let paths = DB.get_global_marks_paths(workspace_id)?; anyhow::Ok((marks, paths)) }) .await .log_err()?; this.update(cx, |this, cx| this.loaded(marks, paths, cx)) .ok() }) .detach(); } fn loaded( &mut self, marks: Vec<SerializedMark>, global_mark_paths: Vec<(String, Arc<Path>)>, cx: &mut Context<Self>, ) { let Some(project) = self.project(cx) else { return; }; for mark in marks { self.serialized_marks .entry(mark.path) .or_default() .insert(mark.name, mark.points); } for (name, path) in global_mark_paths { self.global_marks .insert(name, MarkLocation::Path(path.clone())); let project_path = project .read(cx) .worktrees(cx) .filter_map(|worktree| { let relative = path.strip_prefix(worktree.read(cx).abs_path()).ok()?; let path = RelPath::new(relative, worktree.read(cx).path_style()).log_err()?; Some(ProjectPath { worktree_id: worktree.read(cx).id(), path: path.into_arc(), }) }) .next(); if let Some(buffer) = project_path .and_then(|project_path| project.read(cx).get_open_buffer(&project_path, cx)) { self.on_buffer_loaded(&buffer, cx) } } } pub fn on_buffer_loaded(&mut self, buffer_handle: &Entity<Buffer>, cx: &mut Context<Self>) { let Some(project) = self.project(cx) else { return; }; let Some(project_path) = buffer_handle.read(cx).project_path(cx) else { return; }; let Some(abs_path) = project.read(cx).absolute_path(&project_path, cx) else { return; }; let abs_path: Arc<Path> = abs_path.into(); let Some(serialized_marks) = self.serialized_marks.get(&abs_path) else { return; }; let mut loaded_marks = HashMap::default(); let buffer = buffer_handle.read(cx); for (name, points) in serialized_marks.iter() { loaded_marks.insert( name.clone(), points .iter() .map(|point| buffer.anchor_before(buffer.clip_point(*point, Bias::Left))) .collect(), ); } self.buffer_marks.insert(buffer.remote_id(), loaded_marks); self.watch_buffer(MarkLocation::Path(abs_path), buffer_handle, cx) } fn serialize_buffer_marks( &mut self, path: Arc<Path>, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) { let new_points: HashMap<String, Vec<Point>> = if let Some(anchors) = self.buffer_marks.get(&buffer.read(cx).remote_id()) { anchors .iter() .map(|(name, anchors)| { ( name.clone(), buffer .read(cx) .summaries_for_anchors::<Point, _>(anchors) .collect(), ) }) .collect() } else { HashMap::default() }; let old_points = self.serialized_marks.get(&path); if old_points == Some(&new_points) { return; } let mut to_write = HashMap::default(); for (key, value) in &new_points { if self.is_global_mark(key) && self.global_marks.get(key) != Some(&MarkLocation::Path(path.clone())) { if let Some(workspace_id) = self.workspace_id(cx) { let path = path.clone(); let key = key.clone(); cx.background_spawn(async move { DB.set_global_mark_path(workspace_id, key, path).await }) .detach_and_log_err(cx); } self.global_marks .insert(key.clone(), MarkLocation::Path(path.clone())); } if old_points.and_then(|o| o.get(key)) != Some(value) { to_write.insert(key.clone(), value.clone()); } } self.serialized_marks.insert(path.clone(), new_points); if let Some(workspace_id) = self.workspace_id(cx) { cx.background_spawn(async move { DB.set_marks(workspace_id, path.clone(), to_write).await?; anyhow::Ok(()) }) .detach_and_log_err(cx); } } fn is_global_mark(&self, key: &str) -> bool { key.chars() .next() .is_some_and(|c| c.is_uppercase() || c.is_digit(10)) } fn rename_buffer( &mut self, old_path: MarkLocation, new_path: Arc<Path>, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) { if let MarkLocation::Buffer(entity_id) = old_path && let Some(old_marks) = self.multibuffer_marks.remove(&entity_id) { let buffer_marks = old_marks .into_iter() .map(|(k, v)| (k, v.into_iter().map(|anchor| anchor.text_anchor).collect())) .collect(); self.buffer_marks .insert(buffer.read(cx).remote_id(), buffer_marks); } self.watch_buffer(MarkLocation::Path(new_path.clone()), buffer, cx); self.serialize_buffer_marks(new_path, buffer, cx); } fn path_for_buffer(&self, buffer: &Entity<Buffer>, cx: &App) -> Option<Arc<Path>> { let project_path = buffer.read(cx).project_path(cx)?; let project = self.project(cx)?; let abs_path = project.read(cx).absolute_path(&project_path, cx)?; Some(abs_path.into()) } fn points_at( &self, location: &MarkLocation, multi_buffer: &Entity<MultiBuffer>, cx: &App, ) -> bool { match location { MarkLocation::Buffer(entity_id) => entity_id == &multi_buffer.entity_id(), MarkLocation::Path(path) => { let Some(singleton) = multi_buffer.read(cx).as_singleton() else { return false; };Lines 1–500 of 1835. Load more
use crate::command::command_interceptor; use crate::motion::MotionKind; use crate::normal::repeat::Replayer; use crate::surrounds::SurroundsType; use crate::{ToggleMarksView, ToggleRegistersView, UseSystemClipboard, Vim, VimAddon, VimSettings}; use crate::{motion::Motion, object::Object}; use anyhow::Result; use collections::HashMap; use command_palette_hooks::{CommandPaletteFilter, GlobalCommandPaletteInterceptor}; use db::{ sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection}, sqlez_macros::sql, }; use editor::display_map::{is_invisible, replacement}; use editor::{Anchor, ClipboardSelection, Editor, MultiBuffer, ToPoint as EditorToPoint}; use gpui::{ Action, App, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, DismissEvent, Entity, EntityId, Global, HighlightStyle, StyledText, Subscription, Task, TextStyle, WeakEntity, }; use language::{Buffer, BufferEvent, BufferId, Chunk, Point}; use multi_buffer::MultiBufferRow; use picker::{Picker, PickerDelegate}; use project::{Project, ProjectItem, ProjectPath}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::borrow::BorrowMut; use std::collections::HashSet; use std::path::Path; use std::{fmt::Display, ops::Range, sync::Arc}; use text::{Bias, ToPoint}; use theme::ThemeSettings; use ui::{ ActiveTheme, Context, Div, FluentBuilder, KeyBinding, ParentElement, SharedString, Styled, StyledTypography, Window, h_flex, rems, }; use util::ResultExt; use util::rel_path::RelPath; use workspace::searchable::Direction; use workspace::{Workspace, WorkspaceDb, WorkspaceId}; #[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)] pub enum Mode { #[default] Normal, Insert, Replace, Visual, VisualLine, VisualBlock, HelixNormal, HelixSelect, } impl Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Mode::Normal => write!(f, "NORMAL"), Mode::Insert => write!(f, "INSERT"), Mode::Replace => write!(f, "REPLACE"), Mode::Visual => write!(f, "VISUAL"), Mode::VisualLine => write!(f, "VISUAL LINE"), Mode::VisualBlock => write!(f, "VISUAL BLOCK"), Mode::HelixNormal => write!(f, "NORMAL"), Mode::HelixSelect => write!(f, "SELECT"), } } } impl Mode { pub fn is_visual(&self) -> bool { match self { Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true, Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false, } } } #[derive(Clone, Debug, PartialEq)] pub enum Operator { Change, Delete, Yank, Replace, Object { around: bool, }, FindForward { before: bool, multiline: bool, }, FindBackward { after: bool, multiline: bool, }, Sneak { first_char: Option<char>, }, SneakBackward { first_char: Option<char>, }, AddSurrounds { // Typically no need to configure this as `SendKeystrokes` can be used - see #23088. target: Option<SurroundsType>, }, ChangeSurrounds { target: Option<Object>, /// Represents whether the opening bracket was used for the target /// object. opening: bool, }, DeleteSurrounds, Mark, Jump { line: bool, }, Indent, Outdent, AutoIndent, Rewrap, ShellCommand, Lowercase, Uppercase, OppositeCase, Rot13, Rot47, Digraph { first_char: Option<char>, }, Literal { prefix: Option<String>, }, Register, RecordRegister, ReplayRegister, ToggleComments, ReplaceWithRegister, Exchange, HelixMatch, HelixNext { around: bool, }, HelixPrevious { around: bool, }, } #[derive(Default, Clone, Debug)] pub enum RecordedSelection { #[default] None, Visual { rows: u32, cols: u32, }, SingleLine { cols: u32, }, VisualBlock { rows: u32, cols: u32, }, VisualLine { rows: u32, }, } #[derive(Default, Clone, Debug)] pub struct Register { pub(crate) text: SharedString, pub(crate) clipboard_selections: Option<Vec<ClipboardSelection>>, } impl From<Register> for ClipboardItem { fn from(register: Register) -> Self { if let Some(clipboard_selections) = register.clipboard_selections { ClipboardItem::new_string_with_json_metadata(register.text.into(), clipboard_selections) } else { ClipboardItem::new_string(register.text.into()) } } } impl From<ClipboardItem> for Register { fn from(item: ClipboardItem) -> Self { // For now, we don't store metadata for multiple entries. match item.entries().first() { Some(ClipboardEntry::String(value)) if item.entries().len() == 1 => Register { text: value.text().to_owned().into(), clipboard_selections: value.metadata_json::<Vec<ClipboardSelection>>(), }, // For now, registers can't store images. This could change in the future. _ => Register::default(), } } } impl From<String> for Register { fn from(text: String) -> Self { Register { text: text.into(), clipboard_selections: None, } } } #[derive(Default)] pub struct VimGlobals { pub last_find: Option<Motion>, pub dot_recording: bool, pub dot_replaying: bool, /// pre_count is the number before an operator is specified (3 in 3d2d) pub pre_count: Option<usize>, /// post_count is the number after an operator is specified (2 in 3d2d) pub post_count: Option<usize>, pub forced_motion: bool, pub stop_recording_after_next_action: bool, pub ignore_current_insertion: bool, pub recording_count: Option<usize>, pub recorded_count: Option<usize>, pub recording_actions: Vec<ReplayableAction>, pub recorded_actions: Vec<ReplayableAction>, pub recorded_selection: RecordedSelection, pub recording_register: Option<char>, pub last_recorded_register: Option<char>, pub last_replayed_register: Option<char>, pub replayer: Option<Replayer>, pub last_yank: Option<SharedString>, pub registers: HashMap<char, Register>, pub recordings: HashMap<char, Vec<ReplayableAction>>, pub focused_vim: Option<WeakEntity<Vim>>, pub marks: HashMap<EntityId, Entity<MarksState>>, } pub struct MarksState { workspace: WeakEntity<Workspace>, multibuffer_marks: HashMap<EntityId, HashMap<String, Vec<Anchor>>>, buffer_marks: HashMap<BufferId, HashMap<String, Vec<text::Anchor>>>, watched_buffers: HashMap<BufferId, (MarkLocation, Subscription, Subscription)>, serialized_marks: HashMap<Arc<Path>, HashMap<String, Vec<Point>>>, global_marks: HashMap<String, MarkLocation>, _subscription: Subscription, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum MarkLocation { Buffer(EntityId), Path(Arc<Path>), } pub enum Mark { Local(Vec<Anchor>), Buffer(EntityId, Vec<Anchor>), Path(Arc<Path>, Vec<Point>), } impl MarksState { pub fn new(workspace: &Workspace, cx: &mut App) -> Entity<MarksState> { cx.new(|cx| { let buffer_store = workspace.project().read(cx).buffer_store().clone(); let subscription = cx.subscribe(&buffer_store, move |this: &mut Self, _, event, cx| { if let project::buffer_store::BufferStoreEvent::BufferAdded(buffer) = event { this.on_buffer_loaded(buffer, cx); } }); let mut this = Self { workspace: workspace.weak_handle(), multibuffer_marks: HashMap::default(), buffer_marks: HashMap::default(), watched_buffers: HashMap::default(), serialized_marks: HashMap::default(), global_marks: HashMap::default(), _subscription: subscription, }; this.load(cx); this }) } fn workspace_id(&self, cx: &App) -> Option<WorkspaceId> { self.workspace .read_with(cx, |workspace, _| workspace.database_id()) .ok() .flatten() } fn project(&self, cx: &App) -> Option<Entity<Project>> { self.workspace .read_with(cx, |workspace, _| workspace.project().clone()) .ok() } fn load(&mut self, cx: &mut Context<Self>) { cx.spawn(async move |this, cx| { let Some(workspace_id) = this.update(cx, |this, cx| this.workspace_id(cx)).ok()? else { return None; }; let (marks, paths) = cx .background_spawn(async move { let marks = DB.get_marks(workspace_id)?; let paths = DB.get_global_marks_paths(workspace_id)?; anyhow::Ok((marks, paths)) }) .await .log_err()?; this.update(cx, |this, cx| this.loaded(marks, paths, cx)) .ok() }) .detach(); } fn loaded( &mut self, marks: Vec<SerializedMark>, global_mark_paths: Vec<(String, Arc<Path>)>, cx: &mut Context<Self>, ) { let Some(project) = self.project(cx) else { return; }; for mark in marks { self.serialized_marks .entry(mark.path) .or_default() .insert(mark.name, mark.points); } for (name, path) in global_mark_paths { self.global_marks .insert(name, MarkLocation::Path(path.clone())); let project_path = project .read(cx) .worktrees(cx) .filter_map(|worktree| { let relative = path.strip_prefix(worktree.read(cx).abs_path()).ok()?; let path = RelPath::new(relative, worktree.read(cx).path_style()).log_err()?; Some(ProjectPath { worktree_id: worktree.read(cx).id(), path: path.into_arc(), }) }) .next(); if let Some(buffer) = project_path .and_then(|project_path| project.read(cx).get_open_buffer(&project_path, cx)) { self.on_buffer_loaded(&buffer, cx) } } } pub fn on_buffer_loaded(&mut self, buffer_handle: &Entity<Buffer>, cx: &mut Context<Self>) { let Some(project) = self.project(cx) else { return; }; let Some(project_path) = buffer_handle.read(cx).project_path(cx) else { return; }; let Some(abs_path) = project.read(cx).absolute_path(&project_path, cx) else { return; }; let abs_path: Arc<Path> = abs_path.into(); let Some(serialized_marks) = self.serialized_marks.get(&abs_path) else { return; }; let mut loaded_marks = HashMap::default(); let buffer = buffer_handle.read(cx); for (name, points) in serialized_marks.iter() { loaded_marks.insert( name.clone(), points .iter() .map(|point| buffer.anchor_before(buffer.clip_point(*point, Bias::Left))) .collect(), ); } self.buffer_marks.insert(buffer.remote_id(), loaded_marks); self.watch_buffer(MarkLocation::Path(abs_path), buffer_handle, cx) } fn serialize_buffer_marks( &mut self, path: Arc<Path>, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) { let new_points: HashMap<String, Vec<Point>> = if let Some(anchors) = self.buffer_marks.get(&buffer.read(cx).remote_id()) { anchors .iter() .map(|(name, anchors)| { ( name.clone(), buffer .read(cx) .summaries_for_anchors::<Point, _>(anchors) .collect(), ) }) .collect() } else { HashMap::default() }; let old_points = self.serialized_marks.get(&path); if old_points == Some(&new_points) { return; } let mut to_write = HashMap::default(); for (key, value) in &new_points { if self.is_global_mark(key) && self.global_marks.get(key) != Some(&MarkLocation::Path(path.clone())) { if let Some(workspace_id) = self.workspace_id(cx) { let path = path.clone(); let key = key.clone(); cx.background_spawn(async move { DB.set_global_mark_path(workspace_id, key, path).await }) .detach_and_log_err(cx); } self.global_marks .insert(key.clone(), MarkLocation::Path(path.clone())); } if old_points.and_then(|o| o.get(key)) != Some(value) { to_write.insert(key.clone(), value.clone()); } } self.serialized_marks.insert(path.clone(), new_points); if let Some(workspace_id) = self.workspace_id(cx) { cx.background_spawn(async move { DB.set_marks(workspace_id, path.clone(), to_write).await?; anyhow::Ok(()) }) .detach_and_log_err(cx); } } fn is_global_mark(&self, key: &str) -> bool { key.chars() .next() .is_some_and(|c| c.is_uppercase() || c.is_digit(10)) } fn rename_buffer( &mut self, old_path: MarkLocation, new_path: Arc<Path>, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) { if let MarkLocation::Buffer(entity_id) = old_path && let Some(old_marks) = self.multibuffer_marks.remove(&entity_id) { let buffer_marks = old_marks .into_iter() .map(|(k, v)| (k, v.into_iter().map(|anchor| anchor.text_anchor).collect())) .collect(); self.buffer_marks .insert(buffer.read(cx).remote_id(), buffer_marks); } self.watch_buffer(MarkLocation::Path(new_path.clone()), buffer, cx); self.serialize_buffer_marks(new_path, buffer, cx); } fn path_for_buffer(&self, buffer: &Entity<Buffer>, cx: &App) -> Option<Arc<Path>> { let project_path = buffer.read(cx).project_path(cx)?; let project = self.project(cx)?; let abs_path = project.read(cx).absolute_path(&project_path, cx)?; Some(abs_path.into()) } fn points_at( &self, location: &MarkLocation, multi_buffer: &Entity<MultiBuffer>, cx: &App, ) -> bool { match location { MarkLocation::Buffer(entity_id) => entity_id == &multi_buffer.entity_id(), MarkLocation::Path(path) => { let Some(singleton) = multi_buffer.read(cx).as_singleton() else { return false; };