use crate::commands::{
AutoPairsConfig, Command, CommandExecutor, CommandResult, CursorCommand, EditCommand,
TextEditSpec, UndoHistoryRestoreError, UndoHistorySnapshot,
};
use crate::decorations::{Decoration, DecorationLayerId};
use crate::delta::TextDelta;
use crate::intervals::FoldRegion;
use crate::processing::ProcessingEdit;
use crate::search::{SearchError, SearchMatch, SearchOptions, find_all};
use crate::selection_set::selection_direction;
use crate::snippets::SnippetSession;
use crate::state::CursorState;
use crate::{AnchorBias, TextAnchor};
use crate::{
IndentationConfig, LineEnding, LineIndex, Position, Selection, SelectionDirection,
TabKeyBehavior, ViewCommand,
};
use crate::{StateChange, StateChangeCallback, StateChangeType, WrapIndent, WrapMode};
use std::collections::{BTreeMap, HashMap};
use std::ops::Range;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BufferId(u64);
impl BufferId {
pub const fn from_raw(id: u64) -> Self {
Self(id)
}
pub fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ViewId(u64);
impl ViewId {
pub const fn from_raw(id: u64) -> Self {
Self(id)
}
pub fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone)]
pub struct BufferMetadata {
pub uri: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpenBufferResult {
pub buffer_id: BufferId,
pub view_id: ViewId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct JumpTarget {
pub buffer_id: BufferId,
pub position: Position,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ViewCore {
cursor_position: Position,
selection: Option<Selection>,
secondary_selections: Vec<Selection>,
viewport_width: usize,
wrap_mode: WrapMode,
wrap_indent: WrapIndent,
tab_width: usize,
tab_key_behavior: TabKeyBehavior,
indentation_config: IndentationConfig,
auto_pairs: AutoPairsConfig,
snippet_session: Option<SnippetSession>,
preferred_x_cells: Option<usize>,
}
impl ViewCore {
fn from_executor(executor: &CommandExecutor) -> Self {
let editor = executor.editor();
Self {
cursor_position: editor.cursor_position(),
selection: editor.selection().cloned(),
secondary_selections: editor.secondary_selections().to_vec(),
viewport_width: editor.viewport_width(),
wrap_mode: editor.layout_engine().wrap_mode(),
wrap_indent: editor.layout_engine().wrap_indent(),
tab_width: editor.layout_engine().tab_width(),
tab_key_behavior: executor.tab_key_behavior(),
indentation_config: executor.indentation_config().clone(),
auto_pairs: executor.auto_pairs_config().clone(),
snippet_session: executor.snippet_session().cloned(),
preferred_x_cells: executor.preferred_x_cells(),
}
}
fn apply_to_executor(&self, executor: &mut CommandExecutor) {
let mut invalidate_visual_rows = false;
let editor = executor.editor_mut();
editor.set_cursor_state(
self.cursor_position,
self.selection.clone(),
self.secondary_selections.clone(),
);
if editor.viewport_width() != self.viewport_width {
invalidate_visual_rows = true;
}
let before_wrap_mode = editor.layout_engine().wrap_mode();
let before_wrap_indent = editor.layout_engine().wrap_indent();
let before_tab_width = editor.layout_engine().tab_width();
let before_viewport_width = editor.layout_engine().viewport_width();
if before_wrap_mode != self.wrap_mode
|| before_wrap_indent != self.wrap_indent
|| before_tab_width != self.tab_width
|| before_viewport_width != self.viewport_width
{
invalidate_visual_rows = true;
}
if invalidate_visual_rows {
editor.set_view_options(
self.viewport_width,
self.wrap_mode,
self.wrap_indent,
self.tab_width,
);
}
executor.set_tab_key_behavior(self.tab_key_behavior);
executor.set_indentation_config(self.indentation_config.clone());
executor.set_auto_pairs_config(self.auto_pairs.clone());
executor.set_snippet_session(self.snippet_session.clone());
executor.set_preferred_x_cells(self.preferred_x_cells);
}
}
struct BufferEntry {
meta: BufferMetadata,
executor: CommandExecutor,
version: u64,
last_text_delta: Option<Arc<TextDelta>>,
bookmarks: BookmarkSet,
marks: MarkSet,
}
struct ViewEntry {
buffer: BufferId,
core: ViewCore,
version: u64,
callbacks: Vec<StateChangeCallback>,
scroll_top: usize,
scroll_sub_row_offset: u16,
overscan_rows: usize,
viewport_height: Option<usize>,
last_text_delta: Option<Arc<TextDelta>>,
jump_list: JumpList,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkspaceError {
UriAlreadyOpen(String),
BufferNotFound(BufferId),
ViewNotFound(ViewId),
CommandFailed {
view: ViewId,
message: String,
},
ApplyEditsFailed {
buffer: BufferId,
message: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkspaceUndoHistoryRestoreError {
BufferNotFound(BufferId),
RestoreFailed {
buffer: BufferId,
error: UndoHistoryRestoreError,
},
}
impl std::fmt::Display for WorkspaceUndoHistoryRestoreError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BufferNotFound(id) => write!(f, "Buffer not found (id={})", id.get()),
Self::RestoreFailed { buffer, error } => {
write!(
f,
"Restore undo history failed (buffer={}): {}",
buffer.get(),
error
)
}
}
}
}
impl std::error::Error for WorkspaceUndoHistoryRestoreError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceSearchResult {
pub id: BufferId,
pub uri: Option<String>,
pub matches: Vec<SearchMatch>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ViewSmoothScrollState {
pub top_visual_row: usize,
pub sub_row_offset: u16,
pub overscan_rows: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceViewportState {
pub width: usize,
pub height: Option<usize>,
pub scroll_top: usize,
pub visible_lines: Range<usize>,
pub total_visual_lines: usize,
pub smooth_scroll: ViewSmoothScrollState,
pub prefetch_lines: Range<usize>,
}
fn apply_char_offset_delta(mut offset: usize, delta: &TextDelta) -> usize {
for edit in &delta.edits {
let start = edit.start;
let end = edit.end();
let deleted_len = edit.deleted_len();
let inserted_len = edit.inserted_len();
if offset < start {
continue;
}
if offset < end {
offset = start.saturating_add(inserted_len);
continue;
}
if inserted_len >= deleted_len {
offset = offset.saturating_add(inserted_len - deleted_len);
} else {
offset = offset.saturating_sub(deleted_len - inserted_len);
}
}
offset
}
fn apply_position_delta(
old_index: &LineIndex,
new_index: &LineIndex,
pos: Position,
delta: &TextDelta,
) -> Position {
let before = old_index.position_to_char_offset(pos.line, pos.column);
let after = apply_char_offset_delta(before, delta);
let (line, column) = new_index.char_offset_to_position(after);
Position::new(line, column)
}
fn apply_selection_delta(
old_index: &LineIndex,
new_index: &LineIndex,
selection: &Selection,
delta: &TextDelta,
) -> Selection {
let start = apply_position_delta(old_index, new_index, selection.start, delta);
let end = apply_position_delta(old_index, new_index, selection.end, delta);
Selection {
start,
end,
direction: selection_direction(start, end),
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct BookmarkSet {
anchors: Vec<TextAnchor>,
}
impl BookmarkSet {
fn toggle_line_start(&mut self, line_start_offset: usize) -> bool {
let anchor = TextAnchor::new(line_start_offset, AnchorBias::Left);
match self
.anchors
.binary_search_by_key(&anchor.offset, |a| a.offset)
{
Ok(idx) => {
self.anchors.remove(idx);
false
}
Err(idx) => {
self.anchors.insert(idx, anchor);
true
}
}
}
fn clear(&mut self) {
self.anchors.clear();
}
fn apply_delta(&mut self, delta: &TextDelta) {
for a in &mut self.anchors {
a.apply_delta(delta);
}
self.anchors.sort_by_key(|a| a.offset);
self.anchors.dedup_by_key(|a| a.offset);
}
fn line_numbers(&self, line_index: &LineIndex) -> Vec<usize> {
let mut lines: Vec<usize> = self
.anchors
.iter()
.map(|a| line_index.char_offset_to_position(a.offset).0)
.collect();
lines.sort_unstable();
lines.dedup();
lines
}
fn next_after_line_start(&self, current_line_start: usize) -> Option<TextAnchor> {
self.anchors
.iter()
.copied()
.find(|a| a.offset > current_line_start)
.or_else(|| self.anchors.first().copied())
}
fn prev_before_line_start(&self, current_line_start: usize) -> Option<TextAnchor> {
self.anchors
.iter()
.copied()
.rfind(|a| a.offset < current_line_start)
.or_else(|| self.anchors.last().copied())
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct MarkSet {
marks: BTreeMap<String, TextAnchor>,
}
impl MarkSet {
fn set(&mut self, name: String, offset: usize) {
self.marks
.insert(name, TextAnchor::new(offset, AnchorBias::Right));
}
fn get(&self, name: &str) -> Option<TextAnchor> {
self.marks.get(name).copied()
}
fn remove(&mut self, name: &str) -> bool {
self.marks.remove(name).is_some()
}
fn clear(&mut self) {
self.marks.clear();
}
fn names(&self) -> Vec<String> {
self.marks.keys().cloned().collect()
}
fn apply_delta(&mut self, delta: &TextDelta) {
for anchor in self.marks.values_mut() {
anchor.apply_delta(delta);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct JumpEntry {
buffer_id: BufferId,
anchor: TextAnchor,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct JumpList {
back: Vec<JumpEntry>,
forward: Vec<JumpEntry>,
max_len: usize,
}
impl JumpList {
fn new(max_len: usize) -> Self {
Self {
back: Vec::new(),
forward: Vec::new(),
max_len: max_len.max(1),
}
}
fn record(&mut self, entry: JumpEntry) {
if self.back.last().is_some_and(|last| *last == entry) {
return;
}
self.back.push(entry);
self.forward.clear();
if self.back.len() > self.max_len {
let overflow = self.back.len() - self.max_len;
self.back.drain(0..overflow);
}
}
fn back(&mut self, current: JumpEntry) -> Option<JumpEntry> {
let target = self.back.pop()?;
if !self.forward.last().is_some_and(|last| *last == current) {
self.forward.push(current);
}
Some(target)
}
fn forward(&mut self, current: JumpEntry) -> Option<JumpEntry> {
let target = self.forward.pop()?;
if !self.back.last().is_some_and(|last| *last == current) {
self.back.push(current);
}
Some(target)
}
fn clear(&mut self) {
self.back.clear();
self.forward.clear();
}
fn apply_delta(&mut self, buffer_id: BufferId, delta: &TextDelta) {
for entry in self
.back
.iter_mut()
.chain(self.forward.iter_mut())
.filter(|e| e.buffer_id == buffer_id)
{
entry.anchor.apply_delta(delta);
}
}
}
#[derive(Default)]
pub struct Workspace {
next_buffer_id: u64,
buffers: BTreeMap<BufferId, BufferEntry>,
uri_to_buffer: HashMap<String, BufferId>,
next_view_id: u64,
views: BTreeMap<ViewId, ViewEntry>,
active_view: Option<ViewId>,
intelligence: crate::WorkspaceIntelligence,
}
impl std::fmt::Debug for Workspace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Workspace")
.field("buffer_count", &self.buffers.len())
.field("view_count", &self.views.len())
.field("uri_count", &self.uri_to_buffer.len())
.field("active_view", &self.active_view)
.field("intelligence_set_count", &self.intelligence.len())
.finish()
}
}
impl Workspace {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.buffers.len()
}
pub fn is_empty(&self) -> bool {
self.buffers.is_empty()
}
pub fn view_count(&self) -> usize {
self.views.len()
}
pub fn active_view_id(&self) -> Option<ViewId> {
self.active_view
}
pub fn active_buffer_id(&self) -> Option<BufferId> {
let view_id = self.active_view?;
self.views.get(&view_id).map(|v| v.buffer)
}
pub fn intelligence(&self) -> &crate::WorkspaceIntelligence {
&self.intelligence
}
pub fn intelligence_mut(&mut self) -> &mut crate::WorkspaceIntelligence {
&mut self.intelligence
}
pub fn set_active_view(&mut self, id: ViewId) -> Result<(), WorkspaceError> {
if !self.views.contains_key(&id) {
return Err(WorkspaceError::ViewNotFound(id));
}
self.active_view = Some(id);
Ok(())
}
pub fn open_buffer(
&mut self,
uri: Option<String>,
text: &str,
viewport_width: usize,
) -> Result<OpenBufferResult, WorkspaceError> {
if let Some(uri) = uri.as_ref()
&& self.uri_to_buffer.contains_key(uri)
{
return Err(WorkspaceError::UriAlreadyOpen(uri.clone()));
}
let buffer_id = BufferId(self.next_buffer_id);
self.next_buffer_id = self.next_buffer_id.saturating_add(1);
let executor = CommandExecutor::new(text, viewport_width);
let meta = BufferMetadata { uri: uri.clone() };
self.buffers.insert(
buffer_id,
BufferEntry {
meta,
executor,
version: 0,
last_text_delta: None,
bookmarks: BookmarkSet::default(),
marks: MarkSet::default(),
},
);
if let Some(uri) = uri {
self.uri_to_buffer.insert(uri, buffer_id);
}
let view_id = self.create_view(buffer_id, viewport_width)?;
if self.active_view.is_none() {
self.active_view = Some(view_id);
}
Ok(OpenBufferResult { buffer_id, view_id })
}
pub fn close_buffer(&mut self, id: BufferId) -> Result<(), WorkspaceError> {
let Some(entry) = self.buffers.remove(&id) else {
return Err(WorkspaceError::BufferNotFound(id));
};
if let Some(uri) = entry.meta.uri.as_ref() {
self.uri_to_buffer.remove(uri);
}
let views_to_remove: Vec<ViewId> = self
.views
.iter()
.filter_map(|(vid, v)| if v.buffer == id { Some(*vid) } else { None })
.collect();
for view_id in views_to_remove {
self.views.remove(&view_id);
}
if self
.active_view
.is_some_and(|active| !self.views.contains_key(&active))
{
self.active_view = self.views.keys().next().copied();
}
Ok(())
}
pub fn close_view(&mut self, id: ViewId) -> Result<(), WorkspaceError> {
let Some(view) = self.views.remove(&id) else {
return Err(WorkspaceError::ViewNotFound(id));
};
if self.active_view == Some(id) {
self.active_view = self.views.keys().next().copied();
}
let still_has_views = self.views.values().any(|v| v.buffer == view.buffer);
if !still_has_views {
self.close_buffer(view.buffer)?;
}
Ok(())
}
pub fn buffer_ids(&self) -> Vec<BufferId> {
let mut ids: Vec<BufferId> = self.buffers.keys().copied().collect();
ids.sort_by_key(|id| id.get());
ids
}
pub fn view_ids(&self) -> Vec<ViewId> {
let mut ids: Vec<ViewId> = self.views.keys().copied().collect();
ids.sort_by_key(|id| id.get());
ids
}
pub fn create_view(
&mut self,
buffer: BufferId,
viewport_width: usize,
) -> Result<ViewId, WorkspaceError> {
let Some(buffer_entry) = self.buffers.get_mut(&buffer) else {
return Err(WorkspaceError::BufferNotFound(buffer));
};
let mut core = ViewCore::from_executor(&buffer_entry.executor);
core.cursor_position = Position::new(0, 0);
core.selection = None;
core.secondary_selections.clear();
core.viewport_width = viewport_width.max(1);
core.preferred_x_cells = None;
let view_id = ViewId(self.next_view_id);
self.next_view_id = self.next_view_id.saturating_add(1);
self.views.insert(
view_id,
ViewEntry {
buffer,
core,
version: 0,
callbacks: Vec::new(),
scroll_top: 0,
scroll_sub_row_offset: 0,
overscan_rows: 0,
viewport_height: None,
last_text_delta: None,
jump_list: JumpList::new(200),
},
);
Ok(view_id)
}
pub fn buffer_id_for_uri(&self, uri: &str) -> Option<BufferId> {
self.uri_to_buffer.get(uri).copied()
}
pub fn buffer_line_index(&self, buffer_id: BufferId) -> Result<&LineIndex, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.editor().line_index())
}
pub fn buffer_char_count(&self, buffer_id: BufferId) -> Result<usize, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.editor().char_count())
}
pub fn buffer_text_range(
&self,
buffer_id: BufferId,
start: usize,
len: usize,
) -> Result<String, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.editor().text_range(start, len))
}
pub fn buffer_decorations(
&self,
buffer_id: BufferId,
) -> Result<&BTreeMap<DecorationLayerId, Vec<Decoration>>, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.editor().decorations())
}
pub fn folding_regions_for_buffer(
&self,
buffer_id: BufferId,
) -> Result<Vec<FoldRegion>, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer
.executor
.editor()
.folding_manager()
.regions()
.to_vec())
}
pub fn buffer_is_modified(&self, buffer_id: BufferId) -> Result<bool, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(!buffer.executor.is_clean())
}
pub fn line_ending_for_buffer(
&self,
buffer_id: BufferId,
) -> Result<LineEnding, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.line_ending())
}
pub fn set_line_ending_for_buffer(
&mut self,
buffer_id: BufferId,
line_ending: LineEnding,
) -> Result<(), WorkspaceError> {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
buffer.executor.set_line_ending(line_ending);
Ok(())
}
pub fn is_modified_for_view(&self, view_id: ViewId) -> Result<bool, WorkspaceError> {
let buffer_id = self.buffer_id_for_view(view_id)?;
self.buffer_is_modified(buffer_id)
}
pub fn line_ending_for_view(&self, view_id: ViewId) -> Result<LineEnding, WorkspaceError> {
let buffer_id = self.buffer_id_for_view(view_id)?;
self.line_ending_for_buffer(buffer_id)
}
pub fn set_line_ending_for_view(
&mut self,
view_id: ViewId,
line_ending: LineEnding,
) -> Result<(), WorkspaceError> {
let buffer_id = self.buffer_id_for_view(view_id)?;
self.set_line_ending_for_buffer(buffer_id, line_ending)
}
pub fn mark_saved_for_buffer(&mut self, buffer_id: BufferId) -> Result<(), WorkspaceError> {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
buffer.executor.mark_clean();
Ok(())
}
pub fn mark_saved_for_view(&mut self, view_id: ViewId) -> Result<(), WorkspaceError> {
let buffer_id = self.buffer_id_for_view(view_id)?;
self.mark_saved_for_buffer(buffer_id)
}
pub fn undo_history_snapshot_for_buffer(
&self,
buffer_id: BufferId,
) -> Result<UndoHistorySnapshot, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.undo_history_snapshot())
}
pub fn restore_undo_history_for_buffer(
&mut self,
buffer_id: BufferId,
snapshot: UndoHistorySnapshot,
) -> Result<(), WorkspaceUndoHistoryRestoreError> {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceUndoHistoryRestoreError::BufferNotFound(buffer_id));
};
buffer.last_text_delta = None;
for view in self.views.values_mut() {
if view.buffer == buffer_id {
view.last_text_delta = None;
}
}
buffer
.executor
.restore_undo_history(snapshot)
.map_err(|err| WorkspaceUndoHistoryRestoreError::RestoreFailed {
buffer: buffer_id,
error: err,
})?;
Ok(())
}
pub fn buffer_metadata(&self, id: BufferId) -> Option<&BufferMetadata> {
self.buffers.get(&id).map(|e| &e.meta)
}
pub fn buffer_id_for_view(&self, id: ViewId) -> Result<BufferId, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.buffer)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn cursor_position_for_view(&self, id: ViewId) -> Result<Position, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.cursor_position)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn selection_for_view(&self, id: ViewId) -> Result<Option<Selection>, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.selection.clone())
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn tab_width_for_view(&self, id: ViewId) -> Result<usize, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.tab_width)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn viewport_width_for_view(&self, id: ViewId) -> Result<usize, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.viewport_width)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn wrap_mode_for_view(&self, id: ViewId) -> Result<WrapMode, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.wrap_mode)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn wrap_indent_for_view(&self, id: ViewId) -> Result<WrapIndent, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.wrap_indent)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn tab_key_behavior_for_view(&self, id: ViewId) -> Result<TabKeyBehavior, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.tab_key_behavior)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn indentation_config_for_view(
&self,
id: ViewId,
) -> Result<IndentationConfig, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.indentation_config.clone())
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn auto_pairs_config_for_view(
&self,
id: ViewId,
) -> Result<AutoPairsConfig, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.core.auto_pairs.clone())
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn cursor_state_for_view(&self, id: ViewId) -> Result<CursorState, WorkspaceError> {
let Some(view) = self.views.get(&id) else {
return Err(WorkspaceError::ViewNotFound(id));
};
let Some(buffer) = self.buffers.get(&view.buffer) else {
return Err(WorkspaceError::BufferNotFound(view.buffer));
};
let line_index = buffer.executor.editor().line_index();
let mut selections: Vec<Selection> =
Vec::with_capacity(1 + view.core.secondary_selections.len());
let primary = view.core.selection.clone().unwrap_or(Selection {
start: view.core.cursor_position,
end: view.core.cursor_position,
direction: SelectionDirection::Forward,
});
selections.push(primary);
selections.extend(view.core.secondary_selections.iter().cloned());
let (selections, primary_selection_index) =
crate::selection_set::normalize_selections(selections, 0);
let primary = selections
.get(primary_selection_index)
.cloned()
.unwrap_or(Selection {
start: view.core.cursor_position,
end: view.core.cursor_position,
direction: SelectionDirection::Forward,
});
let position = primary.end;
let offset = line_index.position_to_char_offset(position.line, position.column);
let selection = if primary.start == primary.end {
None
} else {
Some(primary)
};
let multi_cursors: Vec<Position> = selections
.iter()
.enumerate()
.filter_map(|(idx, sel)| {
if idx == primary_selection_index {
None
} else {
Some(sel.end)
}
})
.collect();
Ok(CursorState {
position,
offset,
multi_cursors,
selection,
selections,
primary_selection_index,
})
}
pub fn scroll_top_for_view(&self, id: ViewId) -> Result<usize, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.scroll_top)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn scroll_sub_row_offset_for_view(&self, id: ViewId) -> Result<u16, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.scroll_sub_row_offset)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn overscan_rows_for_view(&self, id: ViewId) -> Result<usize, WorkspaceError> {
self.views
.get(&id)
.map(|v| v.overscan_rows)
.ok_or(WorkspaceError::ViewNotFound(id))
}
pub fn smooth_scroll_state_for_view(
&self,
id: ViewId,
) -> Result<ViewSmoothScrollState, WorkspaceError> {
let Some(view) = self.views.get(&id) else {
return Err(WorkspaceError::ViewNotFound(id));
};
Ok(ViewSmoothScrollState {
top_visual_row: view.scroll_top,
sub_row_offset: view.scroll_sub_row_offset,
overscan_rows: view.overscan_rows,
})
}
pub fn set_buffer_uri(
&mut self,
id: BufferId,
uri: Option<String>,
) -> Result<(), WorkspaceError> {
let Some(entry) = self.buffers.get_mut(&id) else {
return Err(WorkspaceError::BufferNotFound(id));
};
if let Some(next) = uri.as_ref()
&& self.uri_to_buffer.contains_key(next)
&& entry.meta.uri.as_deref() != Some(next.as_str())
{
return Err(WorkspaceError::UriAlreadyOpen(next.clone()));
}
if let Some(prev) = entry.meta.uri.take() {
self.uri_to_buffer.remove(&prev);
}
if let Some(next) = uri.clone() {
self.uri_to_buffer.insert(next, id);
}
entry.meta.uri = uri;
Ok(())
}
pub fn view_version(&self, id: ViewId) -> Option<u64> {
self.views.get(&id).map(|v| v.version)
}
pub fn last_text_delta_for_view(&self, id: ViewId) -> Option<&Arc<TextDelta>> {
self.views.get(&id)?.last_text_delta.as_ref()
}
pub fn take_last_text_delta_for_view(&mut self, id: ViewId) -> Option<Arc<TextDelta>> {
self.views.get_mut(&id)?.last_text_delta.take()
}
pub fn take_last_text_delta_for_buffer(
&mut self,
id: BufferId,
) -> Result<Option<Arc<TextDelta>>, WorkspaceError> {
let Some(buffer) = self.buffers.get_mut(&id) else {
return Err(WorkspaceError::BufferNotFound(id));
};
Ok(buffer.last_text_delta.take())
}
pub fn subscribe_view<F>(&mut self, id: ViewId, callback: F) -> Result<(), WorkspaceError>
where
F: FnMut(&StateChange) + Send + 'static,
{
let Some(view) = self.views.get_mut(&id) else {
return Err(WorkspaceError::ViewNotFound(id));
};
view.callbacks.push(Box::new(callback));
Ok(())
}
fn notify_view(
view: &mut ViewEntry,
change_type: StateChangeType,
delta: Option<Arc<TextDelta>>,
) {
let old_version = view.version;
view.version = view.version.saturating_add(1);
let mut change = StateChange::new(change_type, old_version, view.version);
if let Some(delta) = delta {
change = change.with_text_delta(delta);
}
for cb in &mut view.callbacks {
cb(&change);
}
}
fn command_change_type(command: &Command) -> Option<StateChangeType> {
match command {
Command::Edit(EditCommand::Delete { length: 0, .. }) => None,
Command::Edit(EditCommand::Replace {
length: 0, text, ..
}) if text.is_empty() => None,
Command::Edit(EditCommand::EndUndoGroup) => None,
Command::Edit(_) => Some(StateChangeType::DocumentModified),
Command::Cursor(
CursorCommand::MoveTo { .. }
| CursorCommand::MoveBy { .. }
| CursorCommand::MoveVisualBy { .. }
| CursorCommand::MoveToVisual { .. }
| CursorCommand::MoveToLineStart
| CursorCommand::MoveToLineEnd
| CursorCommand::MoveToVisualLineStart
| CursorCommand::MoveToVisualLineEnd
| CursorCommand::MoveGraphemeLeft
| CursorCommand::MoveGraphemeRight
| CursorCommand::MoveWordLeft
| CursorCommand::MoveWordRight
| CursorCommand::MoveToMatchingBracket
| CursorCommand::FindNext { .. }
| CursorCommand::FindPrev { .. },
) => Some(StateChangeType::CursorMoved),
Command::Cursor(_) => Some(StateChangeType::SelectionChanged),
Command::View(ViewCommand::ScrollTo { .. } | ViewCommand::GetViewport { .. }) => None,
Command::View(_) => Some(StateChangeType::ViewportChanged),
Command::Style(
crate::StyleCommand::AddStyle { .. }
| crate::StyleCommand::RemoveStyle { .. }
| crate::StyleCommand::UpdateBracketMatchHighlights
| crate::StyleCommand::ClearBracketMatchHighlights,
) => Some(StateChangeType::StyleChanged),
Command::Style(
crate::StyleCommand::Fold { .. }
| crate::StyleCommand::Unfold { .. }
| crate::StyleCommand::UnfoldAll,
) => Some(StateChangeType::FoldingChanged),
}
}
pub fn execute(
&mut self,
view_id: ViewId,
command: Command,
) -> Result<CommandResult, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let change_type = Self::command_change_type(&command);
if change_type.is_none() {
}
let views = &mut self.views;
let buffers = &mut self.buffers;
let Some(view) = views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let before_view_core = view.core.clone();
let before_line_index = buffer.executor.editor().line_index().clone();
let before_char_count = buffer.executor.editor().char_count();
view.core.apply_to_executor(&mut buffer.executor);
let result = buffer.executor.execute(command.clone()).map_err(|err| {
WorkspaceError::CommandFailed {
view: view_id,
message: err.to_string(),
}
})?;
view.core = ViewCore::from_executor(&buffer.executor);
let delta = buffer.executor.take_last_text_delta().map(Arc::new);
let after_char_count = buffer.executor.editor().char_count();
let view_changed = view.core != before_view_core;
let buffer_text_changed = delta.is_some()
|| after_char_count != before_char_count;
let buffer_derived_changed = matches!(command, Command::Style(_));
if !(view_changed || buffer_text_changed || buffer_derived_changed) {
return Ok(result);
}
let change_type = if buffer_text_changed {
StateChangeType::DocumentModified
} else {
change_type.unwrap_or(StateChangeType::ViewportChanged)
};
if buffer_text_changed || buffer_derived_changed {
let delta_arc = delta.clone();
if let Some(delta_arc) = delta_arc {
buffer.last_text_delta = Some(delta_arc.clone());
for other in views.values_mut() {
if other.buffer != buffer_id {
continue;
}
other.last_text_delta = Some(delta_arc.clone());
}
} else {
buffer.last_text_delta = None;
}
if let Some(ref delta_arc) = delta {
let new_index = buffer.executor.editor().line_index();
for (other_id, other) in views.iter_mut() {
if other.buffer != buffer_id || *other_id == view_id {
continue;
}
other.core.cursor_position = apply_position_delta(
&before_line_index,
new_index,
other.core.cursor_position,
delta_arc,
);
if let Some(ref sel) = other.core.selection {
other.core.selection = Some(apply_selection_delta(
&before_line_index,
new_index,
sel,
delta_arc,
));
}
for sel in &mut other.core.secondary_selections {
*sel = apply_selection_delta(&before_line_index, new_index, sel, delta_arc);
}
if let Some(ref mut session) = other.core.snippet_session {
session.apply_delta(delta_arc);
}
}
buffer.bookmarks.apply_delta(delta_arc);
buffer.marks.apply_delta(delta_arc);
for other in views.values_mut() {
if other.buffer != buffer_id {
continue;
}
other.jump_list.apply_delta(buffer_id, delta_arc);
}
}
for other in views.values_mut() {
if other.buffer != buffer_id {
continue;
}
Self::notify_view(other, change_type, delta.clone());
}
if buffer_text_changed && let Some(uri) = buffer.meta.uri.as_deref() {
self.intelligence.mark_stale_for_uri(uri);
}
buffer.version = buffer.version.saturating_add(1);
} else {
Self::notify_view(view, change_type, None);
}
Ok(result)
}
pub fn has_active_snippet_session(&self, view_id: ViewId) -> Result<bool, WorkspaceError> {
let Some(view) = self.views.get(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
Ok(view
.core
.snippet_session
.as_ref()
.map(|s| s.is_active())
.unwrap_or(false))
}
pub fn toggle_bookmark_at_cursor_line(
&mut self,
view_id: ViewId,
) -> Result<bool, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let Some(view) = self.views.get(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let line_start = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(view.core.cursor_position.line, 0);
let added = buffer.bookmarks.toggle_line_start(line_start);
for v in self.views.values_mut() {
if v.buffer == buffer_id {
Self::notify_view(v, StateChangeType::NavigationChanged, None);
}
}
Ok(added)
}
pub fn bookmark_lines(&self, buffer_id: BufferId) -> Result<Vec<usize>, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer
.bookmarks
.line_numbers(buffer.executor.editor().line_index()))
}
pub fn clear_bookmarks(&mut self, buffer_id: BufferId) -> Result<(), WorkspaceError> {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
buffer.bookmarks.clear();
for v in self.views.values_mut() {
if v.buffer == buffer_id {
Self::notify_view(v, StateChangeType::NavigationChanged, None);
}
}
Ok(())
}
fn move_view_cursor_to_anchor(
view: &mut ViewEntry,
buffer: &BufferEntry,
anchor: TextAnchor,
) -> Position {
let (line, column) = buffer
.executor
.editor()
.line_index()
.char_offset_to_position(anchor.offset);
view.core.cursor_position = Position::new(line, column);
view.core.preferred_x_cells = buffer
.executor
.editor()
.logical_position_to_visual(line, column)
.map(|(_, x)| x);
view.core.selection = None;
view.core.secondary_selections.clear();
view.core.cursor_position
}
pub fn goto_next_bookmark(
&mut self,
view_id: ViewId,
) -> Result<Option<Position>, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let current_line_start = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(
self.views
.get(&view_id)
.ok_or(WorkspaceError::ViewNotFound(view_id))?
.core
.cursor_position
.line,
0,
);
let Some(target) = buffer.bookmarks.next_after_line_start(current_line_start) else {
return Ok(None);
};
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let pos = Self::move_view_cursor_to_anchor(view, buffer, target);
Self::notify_view(view, StateChangeType::SelectionChanged, None);
Ok(Some(pos))
}
pub fn goto_prev_bookmark(
&mut self,
view_id: ViewId,
) -> Result<Option<Position>, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let current_line_start = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(
self.views
.get(&view_id)
.ok_or(WorkspaceError::ViewNotFound(view_id))?
.core
.cursor_position
.line,
0,
);
let Some(target) = buffer.bookmarks.prev_before_line_start(current_line_start) else {
return Ok(None);
};
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let pos = Self::move_view_cursor_to_anchor(view, buffer, target);
Self::notify_view(view, StateChangeType::SelectionChanged, None);
Ok(Some(pos))
}
pub fn set_mark_at_cursor(
&mut self,
view_id: ViewId,
name: String,
) -> Result<(), WorkspaceError> {
if name.trim().is_empty() {
return Err(WorkspaceError::CommandFailed {
view: view_id,
message: "Mark name cannot be empty".to_string(),
});
}
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let Some(view) = self.views.get(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let pos = view.core.cursor_position;
let offset = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(pos.line, pos.column);
buffer.marks.set(name, offset);
for v in self.views.values_mut() {
if v.buffer == buffer_id {
Self::notify_view(v, StateChangeType::NavigationChanged, None);
}
}
Ok(())
}
pub fn goto_mark(
&mut self,
view_id: ViewId,
name: &str,
) -> Result<Option<Position>, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let Some(anchor) = buffer.marks.get(name) else {
return Ok(None);
};
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let pos = Self::move_view_cursor_to_anchor(view, buffer, anchor);
Self::notify_view(view, StateChangeType::SelectionChanged, None);
Ok(Some(pos))
}
pub fn clear_mark(&mut self, buffer_id: BufferId, name: &str) -> Result<bool, WorkspaceError> {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let existed = buffer.marks.remove(name);
if existed {
for v in self.views.values_mut() {
if v.buffer == buffer_id {
Self::notify_view(v, StateChangeType::NavigationChanged, None);
}
}
}
Ok(existed)
}
pub fn mark_names(&self, buffer_id: BufferId) -> Result<Vec<String>, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.marks.names())
}
pub fn clear_all_marks(&mut self, buffer_id: BufferId) -> Result<(), WorkspaceError> {
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
buffer.marks.clear();
for v in self.views.values_mut() {
if v.buffer == buffer_id {
Self::notify_view(v, StateChangeType::NavigationChanged, None);
}
}
Ok(())
}
pub fn push_jump_location(&mut self, view_id: ViewId) -> Result<(), WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let pos = view.core.cursor_position;
let offset = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(pos.line, pos.column);
view.jump_list.record(JumpEntry {
buffer_id,
anchor: TextAnchor::new(offset, AnchorBias::Right),
});
Self::notify_view(view, StateChangeType::NavigationChanged, None);
Ok(())
}
pub fn jump_back(&mut self, view_id: ViewId) -> Result<Option<JumpTarget>, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let current_pos = self
.views
.get(&view_id)
.ok_or(WorkspaceError::ViewNotFound(view_id))?
.core
.cursor_position;
let current_offset = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(current_pos.line, current_pos.column);
let current = JumpEntry {
buffer_id,
anchor: TextAnchor::new(current_offset, AnchorBias::Right),
};
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(target) = view.jump_list.back(current) else {
return Ok(None);
};
let Some(target_buffer) = self.buffers.get(&target.buffer_id) else {
Self::notify_view(view, StateChangeType::NavigationChanged, None);
return Ok(None);
};
let (line, column) = target_buffer
.executor
.editor()
.line_index()
.char_offset_to_position(target.anchor.offset);
let target_pos = Position::new(line, column);
let out = JumpTarget {
buffer_id: target.buffer_id,
position: target_pos,
};
if target.buffer_id == buffer_id {
Self::move_view_cursor_to_anchor(view, buffer, target.anchor);
Self::notify_view(view, StateChangeType::SelectionChanged, None);
} else {
Self::notify_view(view, StateChangeType::NavigationChanged, None);
}
Ok(Some(out))
}
pub fn jump_forward(&mut self, view_id: ViewId) -> Result<Option<JumpTarget>, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let current_pos = self
.views
.get(&view_id)
.ok_or(WorkspaceError::ViewNotFound(view_id))?
.core
.cursor_position;
let current_offset = buffer
.executor
.editor()
.line_index()
.position_to_char_offset(current_pos.line, current_pos.column);
let current = JumpEntry {
buffer_id,
anchor: TextAnchor::new(current_offset, AnchorBias::Right),
};
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(target) = view.jump_list.forward(current) else {
return Ok(None);
};
let Some(target_buffer) = self.buffers.get(&target.buffer_id) else {
Self::notify_view(view, StateChangeType::NavigationChanged, None);
return Ok(None);
};
let (line, column) = target_buffer
.executor
.editor()
.line_index()
.char_offset_to_position(target.anchor.offset);
let target_pos = Position::new(line, column);
let out = JumpTarget {
buffer_id: target.buffer_id,
position: target_pos,
};
if target.buffer_id == buffer_id {
Self::move_view_cursor_to_anchor(view, buffer, target.anchor);
Self::notify_view(view, StateChangeType::SelectionChanged, None);
} else {
Self::notify_view(view, StateChangeType::NavigationChanged, None);
}
Ok(Some(out))
}
pub fn clear_jump_list(&mut self, view_id: ViewId) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
view.jump_list.clear();
Self::notify_view(view, StateChangeType::NavigationChanged, None);
Ok(())
}
pub fn apply_jump_target(
&mut self,
view_id: ViewId,
target: JumpTarget,
) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
if view.buffer != target.buffer_id {
return Err(WorkspaceError::CommandFailed {
view: view_id,
message: "JumpTarget buffer does not match view buffer".to_string(),
});
}
let Some(buffer) = self.buffers.get(&view.buffer) else {
return Err(WorkspaceError::BufferNotFound(view.buffer));
};
view.core.cursor_position = target.position;
view.core.preferred_x_cells = buffer
.executor
.editor()
.logical_position_to_visual(target.position.line, target.position.column)
.map(|(_, x)| x);
view.core.selection = None;
view.core.secondary_selections.clear();
Self::notify_view(view, StateChangeType::SelectionChanged, None);
Ok(())
}
pub fn set_viewport_height(
&mut self,
view_id: ViewId,
height: usize,
) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
view.viewport_height = Some(height);
Ok(())
}
pub fn set_scroll_top(
&mut self,
view_id: ViewId,
scroll_top: usize,
) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
view.scroll_top = scroll_top;
Ok(())
}
pub fn set_scroll_sub_row_offset(
&mut self,
view_id: ViewId,
sub_row_offset: u16,
) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
view.scroll_sub_row_offset = sub_row_offset;
Ok(())
}
pub fn set_overscan_rows(
&mut self,
view_id: ViewId,
overscan_rows: usize,
) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
view.overscan_rows = overscan_rows;
Ok(())
}
pub fn set_smooth_scroll_state(
&mut self,
view_id: ViewId,
state: ViewSmoothScrollState,
) -> Result<(), WorkspaceError> {
let Some(view) = self.views.get_mut(&view_id) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
view.scroll_top = state.top_visual_row;
view.scroll_sub_row_offset = state.sub_row_offset;
view.overscan_rows = state.overscan_rows;
Ok(())
}
pub fn viewport_state_for_view(
&mut self,
view_id: ViewId,
) -> Result<WorkspaceViewportState, WorkspaceError> {
let Some((
buffer_id,
view_core,
scroll_top,
viewport_height,
sub_row_offset,
overscan_rows,
)) = self.views.get(&view_id).map(|v| {
(
v.buffer,
v.core.clone(),
v.scroll_top,
v.viewport_height,
v.scroll_sub_row_offset,
v.overscan_rows,
)
})
else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
let editor = buffer.executor.editor();
let total_visual_lines = editor.visual_line_count();
let visible_end = if let Some(height) = viewport_height {
scroll_top.saturating_add(height).min(total_visual_lines)
} else {
total_visual_lines
};
let visible_lines = scroll_top.min(total_visual_lines)..visible_end;
let prefetch_start = visible_lines.start.saturating_sub(overscan_rows);
let prefetch_end = visible_lines
.end
.saturating_add(overscan_rows)
.min(total_visual_lines);
Ok(WorkspaceViewportState {
width: editor.viewport_width(),
height: viewport_height,
scroll_top,
visible_lines,
total_visual_lines,
smooth_scroll: ViewSmoothScrollState {
top_visual_row: scroll_top,
sub_row_offset,
overscan_rows,
},
prefetch_lines: prefetch_start..prefetch_end,
})
}
pub fn total_visual_lines_for_view(
&mut self,
view_id: ViewId,
) -> Result<usize, WorkspaceError> {
Ok(self.viewport_state_for_view(view_id)?.total_visual_lines)
}
pub fn visual_to_logical_for_view(
&mut self,
view_id: ViewId,
visual_row: usize,
) -> Result<(usize, usize), WorkspaceError> {
let Some((buffer_id, view_core)) =
self.views.get(&view_id).map(|v| (v.buffer, v.core.clone()))
else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
Ok(buffer.executor.editor().visual_to_logical_line(visual_row))
}
pub fn logical_to_visual_for_view(
&mut self,
view_id: ViewId,
line: usize,
column: usize,
) -> Result<Option<(usize, usize)>, WorkspaceError> {
let Some((buffer_id, view_core)) =
self.views.get(&view_id).map(|v| (v.buffer, v.core.clone()))
else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
Ok(buffer
.executor
.editor()
.logical_position_to_visual(line, column))
}
pub fn visual_position_to_logical_for_view(
&mut self,
view_id: ViewId,
visual_row: usize,
x_cells: usize,
) -> Result<Option<Position>, WorkspaceError> {
let Some((buffer_id, view_core)) =
self.views.get(&view_id).map(|v| (v.buffer, v.core.clone()))
else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
Ok(buffer
.executor
.editor()
.visual_position_to_logical(visual_row, x_cells))
}
pub fn buffer_text(&self, buffer_id: BufferId) -> Result<String, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
Ok(buffer.executor.editor().get_text())
}
pub fn buffer_text_for_saving(&self, buffer_id: BufferId) -> Result<String, WorkspaceError> {
let Some(buffer) = self.buffers.get(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let text = buffer.executor.editor().get_text();
Ok(buffer.executor.line_ending().apply_to_text(&text))
}
pub fn text_for_saving_for_view(&self, view_id: ViewId) -> Result<String, WorkspaceError> {
let buffer_id = self.buffer_id_for_view(view_id)?;
self.buffer_text_for_saving(buffer_id)
}
pub fn get_viewport_content_styled(
&mut self,
view_id: ViewId,
start_visual_row: usize,
count: usize,
) -> Result<crate::HeadlessGrid, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let view_core = self
.views
.get(&view_id)
.map(|v| v.core.clone())
.ok_or(WorkspaceError::ViewNotFound(view_id))?;
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
Ok(buffer
.executor
.editor()
.get_headless_grid_styled(start_visual_row, count))
}
pub fn get_minimap_content(
&mut self,
view_id: ViewId,
start_visual_row: usize,
count: usize,
) -> Result<crate::MinimapGrid, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let view_core = self
.views
.get(&view_id)
.map(|v| v.core.clone())
.ok_or(WorkspaceError::ViewNotFound(view_id))?;
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
Ok(buffer
.executor
.editor()
.get_minimap_grid(start_visual_row, count))
}
pub fn get_viewport_content_composed(
&mut self,
view_id: ViewId,
start_visual_row: usize,
count: usize,
) -> Result<crate::ComposedGrid, WorkspaceError> {
let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
return Err(WorkspaceError::ViewNotFound(view_id));
};
let view_core = self
.views
.get(&view_id)
.map(|v| v.core.clone())
.ok_or(WorkspaceError::ViewNotFound(view_id))?;
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
view_core.apply_to_executor(&mut buffer.executor);
Ok(buffer
.executor
.editor()
.get_headless_grid_composed(start_visual_row, count))
}
pub fn apply_processing_edits<I>(
&mut self,
buffer_id: BufferId,
edits: I,
) -> Result<(), WorkspaceError>
where
I: IntoIterator<Item = ProcessingEdit>,
{
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let mut style_changed = false;
let mut folding_changed = false;
let mut diagnostics_changed = false;
let mut decorations_changed = false;
let mut symbols_changed = false;
for edit in edits {
match edit {
ProcessingEdit::ReplaceStyleLayer { layer, intervals } => {
buffer
.executor
.editor_mut()
.replace_style_layer(layer, intervals);
style_changed = true;
}
ProcessingEdit::ClearStyleLayer { layer } => {
buffer.executor.editor_mut().clear_style_layer(layer);
style_changed = true;
}
ProcessingEdit::ReplaceFoldingRegions {
regions,
preserve_collapsed,
} => {
buffer
.executor
.editor_mut()
.replace_folding_regions(regions, preserve_collapsed);
folding_changed = true;
}
ProcessingEdit::ClearFoldingRegions => {
buffer.executor.editor_mut().clear_derived_folding_regions();
folding_changed = true;
}
ProcessingEdit::ReplaceDiagnostics { diagnostics } => {
buffer
.executor
.editor_mut()
.replace_diagnostics(diagnostics);
diagnostics_changed = true;
}
ProcessingEdit::ClearDiagnostics => {
buffer.executor.editor_mut().clear_diagnostics();
diagnostics_changed = true;
}
ProcessingEdit::ReplaceDecorations { layer, decorations } => {
buffer
.executor
.editor_mut()
.replace_decorations(layer, decorations);
decorations_changed = true;
}
ProcessingEdit::ClearDecorations { layer } => {
buffer.executor.editor_mut().clear_decorations(layer);
decorations_changed = true;
}
ProcessingEdit::ReplaceDocumentSymbols { symbols } => {
buffer
.executor
.editor_mut()
.replace_document_symbols(symbols);
symbols_changed = true;
}
ProcessingEdit::ClearDocumentSymbols => {
buffer.executor.editor_mut().clear_document_symbols();
symbols_changed = true;
}
}
}
let change_type = if folding_changed {
Some(StateChangeType::FoldingChanged)
} else if style_changed {
Some(StateChangeType::StyleChanged)
} else if decorations_changed {
Some(StateChangeType::DecorationsChanged)
} else if diagnostics_changed {
Some(StateChangeType::DiagnosticsChanged)
} else if symbols_changed {
Some(StateChangeType::SymbolsChanged)
} else {
None
};
if let Some(change_type) = change_type {
for view in self.views.values_mut() {
if view.buffer == buffer_id {
Self::notify_view(view, change_type, None);
}
}
buffer.version = buffer.version.saturating_add(1);
}
Ok(())
}
pub fn search_all_open_buffers(
&self,
query: &str,
options: SearchOptions,
) -> Result<Vec<WorkspaceSearchResult>, SearchError> {
let mut out: Vec<WorkspaceSearchResult> = Vec::new();
for (id, entry) in &self.buffers {
let text = entry.executor.editor().get_text();
let matches = find_all(&text, query, options)?;
if matches.is_empty() {
continue;
}
out.push(WorkspaceSearchResult {
id: *id,
uri: entry.meta.uri.clone(),
matches,
});
}
Ok(out)
}
pub fn apply_text_edits<I>(
&mut self,
edits: I,
) -> Result<Vec<(BufferId, usize)>, WorkspaceError>
where
I: IntoIterator<Item = (BufferId, Vec<TextEditSpec>)>,
{
let mut by_id: BTreeMap<BufferId, Vec<TextEditSpec>> = BTreeMap::new();
for (id, mut buffer_edits) in edits {
by_id.entry(id).or_default().append(&mut buffer_edits);
}
let mut applied: Vec<(BufferId, usize)> = Vec::new();
for (buffer_id, buffer_edits) in by_id {
let edit_count = buffer_edits.len();
if edit_count == 0 {
continue;
}
let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
return Err(WorkspaceError::BufferNotFound(buffer_id));
};
let before_line_index = buffer.executor.editor().line_index().clone();
let before_char_count = buffer.executor.editor().char_count();
let neutral = ViewCore {
cursor_position: Position::new(0, 0),
selection: None,
secondary_selections: Vec::new(),
viewport_width: buffer.executor.editor().viewport_width().max(1),
wrap_mode: buffer.executor.editor().layout_engine().wrap_mode(),
wrap_indent: buffer.executor.editor().layout_engine().wrap_indent(),
tab_width: buffer.executor.editor().layout_engine().tab_width(),
tab_key_behavior: buffer.executor.tab_key_behavior(),
indentation_config: buffer.executor.indentation_config().clone(),
auto_pairs: buffer.executor.auto_pairs_config().clone(),
snippet_session: None,
preferred_x_cells: None,
};
neutral.apply_to_executor(&mut buffer.executor);
buffer
.executor
.execute(Command::Edit(EditCommand::ApplyTextEdits {
edits: buffer_edits,
}))
.map_err(|err| WorkspaceError::ApplyEditsFailed {
buffer: buffer_id,
message: err.to_string(),
})?;
let delta = buffer.executor.take_last_text_delta().map(Arc::new);
let after_char_count = buffer.executor.editor().char_count();
let changed = delta.is_some() || after_char_count != before_char_count;
if changed {
if let Some(uri) = buffer.meta.uri.as_deref() {
self.intelligence.mark_stale_for_uri(uri);
}
if let Some(ref delta_arc) = delta {
buffer.last_text_delta = Some(delta_arc.clone());
let new_index = buffer.executor.editor().line_index();
for view in self.views.values_mut() {
if view.buffer != buffer_id {
continue;
}
view.last_text_delta = Some(delta_arc.clone());
view.core.cursor_position = apply_position_delta(
&before_line_index,
new_index,
view.core.cursor_position,
delta_arc,
);
if let Some(ref sel) = view.core.selection {
view.core.selection = Some(apply_selection_delta(
&before_line_index,
new_index,
sel,
delta_arc,
));
}
for sel in &mut view.core.secondary_selections {
*sel = apply_selection_delta(
&before_line_index,
new_index,
sel,
delta_arc,
);
}
Self::notify_view(
view,
StateChangeType::DocumentModified,
Some(delta_arc.clone()),
);
}
} else {
buffer.last_text_delta = None;
for view in self.views.values_mut() {
if view.buffer == buffer_id {
Self::notify_view(view, StateChangeType::DocumentModified, None);
}
}
}
buffer.version = buffer.version.saturating_add(1);
}
applied.push((buffer_id, edit_count));
}
Ok(applied)
}
}