Skip to main content

editor_core/
workspace.rs

1//! Workspace and multi-buffer / multi-view model.
2//!
3//! `editor-core` is intentionally UI-agnostic, but a full-featured editor typically needs a
4//! kernel-level model for:
5//!
6//! - managing multiple open buffers (text + undo + derived metadata)
7//! - managing multiple views into the same buffer (split panes)
8//!
9//! This module provides a small [`Workspace`] that owns:
10//! - `BufferId` + `CommandExecutor` (buffer text + undo + derived state)
11//! - `ViewId` + per-view state (selections/cursors, wrap config, scroll)
12//!
13//! The workspace executes commands **against a specific view**. Text edits are applied to the
14//! underlying buffer, and any resulting [`crate::TextDelta`] is broadcast to all views of the
15//! same buffer.
16
17use crate::commands::{
18    Command, CommandExecutor, CommandResult, CursorCommand, EditCommand, TextEditSpec,
19};
20use crate::delta::TextDelta;
21use crate::processing::ProcessingEdit;
22use crate::search::{SearchError, SearchMatch, SearchOptions, find_all};
23use crate::selection_set::selection_direction;
24use crate::{LineIndex, Position, Selection, TabKeyBehavior, ViewCommand};
25use crate::{StateChange, StateChangeCallback, StateChangeType, WrapIndent, WrapMode};
26use std::collections::{BTreeMap, HashMap, HashSet};
27use std::ops::Range;
28use std::sync::Arc;
29
30/// Opaque identifier for an open buffer in a [`Workspace`].
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub struct BufferId(u64);
33
34impl BufferId {
35    /// Create a `BufferId` from a raw numeric id.
36    ///
37    /// This is intended for interoperability boundaries (e.g. FFI) that persist ids externally.
38    pub const fn from_raw(id: u64) -> Self {
39        Self(id)
40    }
41
42    /// Get the underlying numeric id.
43    pub fn get(self) -> u64 {
44        self.0
45    }
46}
47
48/// Opaque identifier for a view into a buffer in a [`Workspace`].
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct ViewId(u64);
51
52impl ViewId {
53    /// Create a `ViewId` from a raw numeric id.
54    ///
55    /// This is intended for interoperability boundaries (e.g. FFI) that persist ids externally.
56    pub const fn from_raw(id: u64) -> Self {
57        Self(id)
58    }
59
60    /// Get the underlying numeric id.
61    pub fn get(self) -> u64 {
62        self.0
63    }
64}
65
66/// Metadata attached to a workspace buffer.
67#[derive(Debug, Clone)]
68pub struct BufferMetadata {
69    /// Optional buffer URI/path (host-provided).
70    pub uri: Option<String>,
71}
72
73/// Result of opening a buffer (a buffer always starts with a default view).
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct OpenBufferResult {
76    /// The created buffer id.
77    pub buffer_id: BufferId,
78    /// The initial view id into that buffer.
79    pub view_id: ViewId,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83struct ViewCore {
84    cursor_position: Position,
85    selection: Option<Selection>,
86    secondary_selections: Vec<Selection>,
87    viewport_width: usize,
88    wrap_mode: WrapMode,
89    wrap_indent: WrapIndent,
90    tab_width: usize,
91    tab_key_behavior: TabKeyBehavior,
92    preferred_x_cells: Option<usize>,
93}
94
95impl ViewCore {
96    fn from_executor(executor: &CommandExecutor) -> Self {
97        let editor = executor.editor();
98        Self {
99            cursor_position: editor.cursor_position,
100            selection: editor.selection.clone(),
101            secondary_selections: editor.secondary_selections.clone(),
102            viewport_width: editor.viewport_width,
103            wrap_mode: editor.layout_engine.wrap_mode(),
104            wrap_indent: editor.layout_engine.wrap_indent(),
105            tab_width: editor.layout_engine.tab_width(),
106            tab_key_behavior: executor.tab_key_behavior(),
107            preferred_x_cells: executor.preferred_x_cells(),
108        }
109    }
110
111    fn apply_to_executor(&self, executor: &mut CommandExecutor) {
112        let mut invalidate_visual_rows = false;
113        let editor = executor.editor_mut();
114        editor.cursor_position = self.cursor_position;
115        editor.selection = self.selection.clone();
116        editor.secondary_selections = self.secondary_selections.clone();
117
118        if editor.viewport_width != self.viewport_width {
119            editor.viewport_width = self.viewport_width;
120            invalidate_visual_rows = true;
121        }
122
123        let before_wrap_mode = editor.layout_engine.wrap_mode();
124        let before_wrap_indent = editor.layout_engine.wrap_indent();
125        let before_tab_width = editor.layout_engine.tab_width();
126        let before_viewport_width = editor.layout_engine.viewport_width();
127        editor.layout_engine.set_viewport_width(self.viewport_width);
128        editor.layout_engine.set_wrap_mode(self.wrap_mode);
129        editor.layout_engine.set_wrap_indent(self.wrap_indent);
130        editor.layout_engine.set_tab_width(self.tab_width);
131        if before_wrap_mode != self.wrap_mode
132            || before_wrap_indent != self.wrap_indent
133            || before_tab_width != self.tab_width
134            || before_viewport_width != self.viewport_width
135        {
136            invalidate_visual_rows = true;
137        }
138
139        if invalidate_visual_rows {
140            editor.invalidate_visual_row_index_cache();
141        }
142
143        executor.set_tab_key_behavior(self.tab_key_behavior);
144        executor.set_preferred_x_cells(self.preferred_x_cells);
145    }
146}
147
148struct BufferEntry {
149    meta: BufferMetadata,
150    executor: CommandExecutor,
151    version: u64,
152    last_text_delta: Option<Arc<TextDelta>>,
153}
154
155struct ViewEntry {
156    buffer: BufferId,
157    core: ViewCore,
158    version: u64,
159    callbacks: Vec<StateChangeCallback>,
160    scroll_top: usize,
161    scroll_sub_row_offset: u16,
162    overscan_rows: usize,
163    viewport_height: Option<usize>,
164    last_text_delta: Option<Arc<TextDelta>>,
165}
166
167/// Workspace-level errors.
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub enum WorkspaceError {
170    /// A buffer with this uri already exists.
171    UriAlreadyOpen(String),
172    /// A buffer id was not found.
173    BufferNotFound(BufferId),
174    /// A view id was not found.
175    ViewNotFound(ViewId),
176    /// Executing a command failed.
177    CommandFailed {
178        /// Target view id.
179        view: ViewId,
180        /// Error message.
181        message: String,
182    },
183    /// Applying edits to a buffer failed.
184    ApplyEditsFailed {
185        /// Target buffer id.
186        buffer: BufferId,
187        /// Error message.
188        message: String,
189    },
190}
191
192/// Search matches for a single open buffer in a [`Workspace`].
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct WorkspaceSearchResult {
195    /// Buffer id.
196    pub id: BufferId,
197    /// Optional URI/path metadata.
198    pub uri: Option<String>,
199    /// All matches in this buffer (character offsets, half-open).
200    pub matches: Vec<SearchMatch>,
201}
202
203/// Smooth-scrolling state for a view.
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub struct ViewSmoothScrollState {
206    /// Top visual row anchor.
207    pub top_visual_row: usize,
208    /// Sub-row offset within `top_visual_row` (0..=65535, normalized).
209    pub sub_row_offset: u16,
210    /// Overscan rows for prefetching.
211    pub overscan_rows: usize,
212}
213
214/// Viewport state for a workspace view, including visual totals and smooth-scrolling metadata.
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct WorkspaceViewportState {
217    /// Viewport width (in cells).
218    pub width: usize,
219    /// Viewport height (line count, host-provided).
220    pub height: Option<usize>,
221    /// Current top visual row.
222    pub scroll_top: usize,
223    /// Visible visual range.
224    pub visible_lines: Range<usize>,
225    /// Total visual line count under current view config (wrap + folding aware).
226    pub total_visual_lines: usize,
227    /// Smooth-scroll metadata.
228    pub smooth_scroll: ViewSmoothScrollState,
229    /// Recommended prefetch range using overscan rows.
230    pub prefetch_lines: Range<usize>,
231}
232
233fn apply_char_offset_delta(mut offset: usize, delta: &TextDelta) -> usize {
234    for edit in &delta.edits {
235        let start = edit.start;
236        let end = edit.end();
237        let deleted_len = edit.deleted_len();
238        let inserted_len = edit.inserted_len();
239
240        if offset < start {
241            continue;
242        }
243
244        if offset < end {
245            // If the caret was inside the replaced range, anchor it at the end of the inserted text.
246            offset = start.saturating_add(inserted_len);
247            continue;
248        }
249
250        // After the replaced range: shift by the net length delta.
251        if inserted_len >= deleted_len {
252            offset = offset.saturating_add(inserted_len - deleted_len);
253        } else {
254            offset = offset.saturating_sub(deleted_len - inserted_len);
255        }
256    }
257
258    offset
259}
260
261fn apply_position_delta(
262    old_index: &LineIndex,
263    new_index: &LineIndex,
264    pos: Position,
265    delta: &TextDelta,
266) -> Position {
267    let before = old_index.position_to_char_offset(pos.line, pos.column);
268    let after = apply_char_offset_delta(before, delta);
269    let (line, column) = new_index.char_offset_to_position(after);
270    Position::new(line, column)
271}
272
273fn apply_selection_delta(
274    old_index: &LineIndex,
275    new_index: &LineIndex,
276    selection: &Selection,
277    delta: &TextDelta,
278) -> Selection {
279    let start = apply_position_delta(old_index, new_index, selection.start, delta);
280    let end = apply_position_delta(old_index, new_index, selection.end, delta);
281    Selection {
282        start,
283        end,
284        direction: selection_direction(start, end),
285    }
286}
287
288/// A collection of open buffers and their views.
289#[derive(Default)]
290pub struct Workspace {
291    next_buffer_id: u64,
292    buffers: BTreeMap<BufferId, BufferEntry>,
293    uri_to_buffer: HashMap<String, BufferId>,
294
295    next_view_id: u64,
296    views: BTreeMap<ViewId, ViewEntry>,
297    active_view: Option<ViewId>,
298}
299
300impl std::fmt::Debug for Workspace {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        f.debug_struct("Workspace")
303            .field("buffer_count", &self.buffers.len())
304            .field("view_count", &self.views.len())
305            .field("uri_count", &self.uri_to_buffer.len())
306            .field("active_view", &self.active_view)
307            .finish()
308    }
309}
310
311impl Workspace {
312    /// Create an empty workspace.
313    pub fn new() -> Self {
314        Self::default()
315    }
316
317    /// Returns the number of open buffers.
318    pub fn len(&self) -> usize {
319        self.buffers.len()
320    }
321
322    /// Returns `true` if there are no open buffers.
323    pub fn is_empty(&self) -> bool {
324        self.buffers.is_empty()
325    }
326
327    /// Returns the number of open views.
328    pub fn view_count(&self) -> usize {
329        self.views.len()
330    }
331
332    /// Return the active view id (if any).
333    pub fn active_view_id(&self) -> Option<ViewId> {
334        self.active_view
335    }
336
337    /// Return the active buffer id (if any).
338    pub fn active_buffer_id(&self) -> Option<BufferId> {
339        let view_id = self.active_view?;
340        self.views.get(&view_id).map(|v| v.buffer)
341    }
342
343    /// Set the active view.
344    pub fn set_active_view(&mut self, id: ViewId) -> Result<(), WorkspaceError> {
345        if !self.views.contains_key(&id) {
346            return Err(WorkspaceError::ViewNotFound(id));
347        }
348        self.active_view = Some(id);
349        Ok(())
350    }
351
352    /// Open a new buffer in the workspace, creating an initial view.
353    ///
354    /// - `uri` is optional and host-provided (e.g. `file:///...`).
355    /// - `text` is the initial contents.
356    /// - `viewport_width` is the initial view's wrap width.
357    pub fn open_buffer(
358        &mut self,
359        uri: Option<String>,
360        text: &str,
361        viewport_width: usize,
362    ) -> Result<OpenBufferResult, WorkspaceError> {
363        if let Some(uri) = uri.as_ref()
364            && self.uri_to_buffer.contains_key(uri)
365        {
366            return Err(WorkspaceError::UriAlreadyOpen(uri.clone()));
367        }
368
369        let buffer_id = BufferId(self.next_buffer_id);
370        self.next_buffer_id = self.next_buffer_id.saturating_add(1);
371
372        let executor = CommandExecutor::new(text, viewport_width);
373        let meta = BufferMetadata { uri: uri.clone() };
374        self.buffers.insert(
375            buffer_id,
376            BufferEntry {
377                meta,
378                executor,
379                version: 0,
380                last_text_delta: None,
381            },
382        );
383
384        if let Some(uri) = uri {
385            self.uri_to_buffer.insert(uri, buffer_id);
386        }
387
388        let view_id = self.create_view(buffer_id, viewport_width)?;
389
390        if self.active_view.is_none() {
391            self.active_view = Some(view_id);
392        }
393
394        Ok(OpenBufferResult { buffer_id, view_id })
395    }
396
397    /// Close a buffer (and all its views).
398    pub fn close_buffer(&mut self, id: BufferId) -> Result<(), WorkspaceError> {
399        let Some(entry) = self.buffers.remove(&id) else {
400            return Err(WorkspaceError::BufferNotFound(id));
401        };
402
403        if let Some(uri) = entry.meta.uri.as_ref() {
404            self.uri_to_buffer.remove(uri);
405        }
406
407        let views_to_remove: Vec<ViewId> = self
408            .views
409            .iter()
410            .filter_map(|(vid, v)| if v.buffer == id { Some(*vid) } else { None })
411            .collect();
412        for view_id in views_to_remove {
413            self.views.remove(&view_id);
414        }
415
416        if self
417            .active_view
418            .is_some_and(|active| !self.views.contains_key(&active))
419        {
420            self.active_view = self.views.keys().next().copied();
421        }
422
423        Ok(())
424    }
425
426    /// Close a view. If it was the last view of its buffer, the buffer is also closed.
427    pub fn close_view(&mut self, id: ViewId) -> Result<(), WorkspaceError> {
428        let Some(view) = self.views.remove(&id) else {
429            return Err(WorkspaceError::ViewNotFound(id));
430        };
431
432        if self.active_view == Some(id) {
433            self.active_view = self.views.keys().next().copied();
434        }
435
436        let still_has_views = self.views.values().any(|v| v.buffer == view.buffer);
437        if !still_has_views {
438            self.close_buffer(view.buffer)?;
439        }
440
441        Ok(())
442    }
443
444    /// Create a new view into an existing buffer.
445    pub fn create_view(
446        &mut self,
447        buffer: BufferId,
448        viewport_width: usize,
449    ) -> Result<ViewId, WorkspaceError> {
450        let Some(buffer_entry) = self.buffers.get_mut(&buffer) else {
451            return Err(WorkspaceError::BufferNotFound(buffer));
452        };
453
454        // Create a view state by starting from the executor defaults, but overriding width and
455        // clearing selection/cursors.
456        let mut core = ViewCore::from_executor(&buffer_entry.executor);
457        core.cursor_position = Position::new(0, 0);
458        core.selection = None;
459        core.secondary_selections.clear();
460        core.viewport_width = viewport_width.max(1);
461        core.preferred_x_cells = None;
462
463        let view_id = ViewId(self.next_view_id);
464        self.next_view_id = self.next_view_id.saturating_add(1);
465
466        self.views.insert(
467            view_id,
468            ViewEntry {
469                buffer,
470                core,
471                version: 0,
472                callbacks: Vec::new(),
473                scroll_top: 0,
474                scroll_sub_row_offset: 0,
475                overscan_rows: 0,
476                viewport_height: None,
477                last_text_delta: None,
478            },
479        );
480
481        Ok(view_id)
482    }
483
484    /// Look up a buffer by uri.
485    pub fn buffer_id_for_uri(&self, uri: &str) -> Option<BufferId> {
486        self.uri_to_buffer.get(uri).copied()
487    }
488
489    /// Get a buffer's metadata.
490    pub fn buffer_metadata(&self, id: BufferId) -> Option<&BufferMetadata> {
491        self.buffers.get(&id).map(|e| &e.meta)
492    }
493
494    /// Get the buffer id that a view is pointing at.
495    pub fn buffer_id_for_view(&self, id: ViewId) -> Result<BufferId, WorkspaceError> {
496        self.views
497            .get(&id)
498            .map(|v| v.buffer)
499            .ok_or(WorkspaceError::ViewNotFound(id))
500    }
501
502    /// Get the primary cursor position for a view.
503    pub fn cursor_position_for_view(&self, id: ViewId) -> Result<Position, WorkspaceError> {
504        self.views
505            .get(&id)
506            .map(|v| v.core.cursor_position)
507            .ok_or(WorkspaceError::ViewNotFound(id))
508    }
509
510    /// Get the primary selection for a view (None means "empty selection / caret only").
511    pub fn selection_for_view(&self, id: ViewId) -> Result<Option<Selection>, WorkspaceError> {
512        self.views
513            .get(&id)
514            .map(|v| v.core.selection.clone())
515            .ok_or(WorkspaceError::ViewNotFound(id))
516    }
517
518    /// Get the scroll position (top visual row) for a view.
519    pub fn scroll_top_for_view(&self, id: ViewId) -> Result<usize, WorkspaceError> {
520        self.views
521            .get(&id)
522            .map(|v| v.scroll_top)
523            .ok_or(WorkspaceError::ViewNotFound(id))
524    }
525
526    /// Get the sub-row smooth-scroll offset for a view.
527    pub fn scroll_sub_row_offset_for_view(&self, id: ViewId) -> Result<u16, WorkspaceError> {
528        self.views
529            .get(&id)
530            .map(|v| v.scroll_sub_row_offset)
531            .ok_or(WorkspaceError::ViewNotFound(id))
532    }
533
534    /// Get overscan rows for a view.
535    pub fn overscan_rows_for_view(&self, id: ViewId) -> Result<usize, WorkspaceError> {
536        self.views
537            .get(&id)
538            .map(|v| v.overscan_rows)
539            .ok_or(WorkspaceError::ViewNotFound(id))
540    }
541
542    /// Get smooth-scroll state for a view.
543    pub fn smooth_scroll_state_for_view(
544        &self,
545        id: ViewId,
546    ) -> Result<ViewSmoothScrollState, WorkspaceError> {
547        let Some(view) = self.views.get(&id) else {
548            return Err(WorkspaceError::ViewNotFound(id));
549        };
550        Ok(ViewSmoothScrollState {
551            top_visual_row: view.scroll_top,
552            sub_row_offset: view.scroll_sub_row_offset,
553            overscan_rows: view.overscan_rows,
554        })
555    }
556
557    /// Update a buffer's uri/path.
558    pub fn set_buffer_uri(
559        &mut self,
560        id: BufferId,
561        uri: Option<String>,
562    ) -> Result<(), WorkspaceError> {
563        let Some(entry) = self.buffers.get_mut(&id) else {
564            return Err(WorkspaceError::BufferNotFound(id));
565        };
566
567        if let Some(next) = uri.as_ref()
568            && self.uri_to_buffer.contains_key(next)
569            && entry.meta.uri.as_deref() != Some(next.as_str())
570        {
571            return Err(WorkspaceError::UriAlreadyOpen(next.clone()));
572        }
573
574        if let Some(prev) = entry.meta.uri.take() {
575            self.uri_to_buffer.remove(&prev);
576        }
577
578        if let Some(next) = uri.clone() {
579            self.uri_to_buffer.insert(next, id);
580        }
581
582        entry.meta.uri = uri;
583        Ok(())
584    }
585
586    /// Get a view's current version (increments on view-local changes and buffer changes).
587    pub fn view_version(&self, id: ViewId) -> Option<u64> {
588        self.views.get(&id).map(|v| v.version)
589    }
590
591    /// Get the last broadcast text delta for this view (if any).
592    pub fn last_text_delta_for_view(&self, id: ViewId) -> Option<&Arc<TextDelta>> {
593        self.views.get(&id)?.last_text_delta.as_ref()
594    }
595
596    /// Take the last broadcast text delta for this view (if any).
597    pub fn take_last_text_delta_for_view(&mut self, id: ViewId) -> Option<Arc<TextDelta>> {
598        self.views.get_mut(&id)?.last_text_delta.take()
599    }
600
601    /// Take the last text delta for a buffer (if any).
602    ///
603    /// This is useful for incremental consumers (e.g. LSP sync) that want to observe each buffer
604    /// edit exactly once, regardless of how many views exist for that buffer.
605    pub fn take_last_text_delta_for_buffer(
606        &mut self,
607        id: BufferId,
608    ) -> Result<Option<Arc<TextDelta>>, WorkspaceError> {
609        let Some(buffer) = self.buffers.get_mut(&id) else {
610            return Err(WorkspaceError::BufferNotFound(id));
611        };
612        Ok(buffer.last_text_delta.take())
613    }
614
615    /// Subscribe to changes for a view.
616    pub fn subscribe_view<F>(&mut self, id: ViewId, callback: F) -> Result<(), WorkspaceError>
617    where
618        F: FnMut(&StateChange) + Send + 'static,
619    {
620        let Some(view) = self.views.get_mut(&id) else {
621            return Err(WorkspaceError::ViewNotFound(id));
622        };
623
624        view.callbacks.push(Box::new(callback));
625        Ok(())
626    }
627
628    fn notify_view(
629        view: &mut ViewEntry,
630        change_type: StateChangeType,
631        delta: Option<Arc<TextDelta>>,
632    ) {
633        let old_version = view.version;
634        view.version = view.version.saturating_add(1);
635
636        let mut change = StateChange::new(change_type, old_version, view.version);
637        if let Some(delta) = delta {
638            change = change.with_text_delta(delta);
639        }
640
641        for cb in &mut view.callbacks {
642            cb(&change);
643        }
644    }
645
646    fn command_change_type(command: &Command) -> Option<StateChangeType> {
647        match command {
648            Command::Edit(EditCommand::InsertText { text }) if text.is_empty() => None,
649            Command::Edit(EditCommand::Delete { length: 0, .. }) => None,
650            Command::Edit(EditCommand::Replace {
651                length: 0, text, ..
652            }) if text.is_empty() => None,
653            Command::Edit(EditCommand::EndUndoGroup) => None,
654            Command::Edit(_) => Some(StateChangeType::DocumentModified),
655            Command::Cursor(
656                CursorCommand::MoveTo { .. }
657                | CursorCommand::MoveBy { .. }
658                | CursorCommand::MoveVisualBy { .. }
659                | CursorCommand::MoveToVisual { .. }
660                | CursorCommand::MoveToLineStart
661                | CursorCommand::MoveToLineEnd
662                | CursorCommand::MoveToVisualLineStart
663                | CursorCommand::MoveToVisualLineEnd
664                | CursorCommand::MoveGraphemeLeft
665                | CursorCommand::MoveGraphemeRight
666                | CursorCommand::MoveWordLeft
667                | CursorCommand::MoveWordRight
668                | CursorCommand::FindNext { .. }
669                | CursorCommand::FindPrev { .. },
670            ) => Some(StateChangeType::CursorMoved),
671            Command::Cursor(_) => Some(StateChangeType::SelectionChanged),
672            Command::View(ViewCommand::ScrollTo { .. } | ViewCommand::GetViewport { .. }) => None,
673            Command::View(_) => Some(StateChangeType::ViewportChanged),
674            Command::Style(
675                crate::StyleCommand::AddStyle { .. } | crate::StyleCommand::RemoveStyle { .. },
676            ) => Some(StateChangeType::StyleChanged),
677            Command::Style(
678                crate::StyleCommand::Fold { .. }
679                | crate::StyleCommand::Unfold { .. }
680                | crate::StyleCommand::UnfoldAll,
681            ) => Some(StateChangeType::FoldingChanged),
682        }
683    }
684
685    /// Execute a command against a specific view.
686    ///
687    /// - Cursor/selection state is view-local.
688    /// - Text edits and derived-state edits are applied to the underlying buffer.
689    /// - Any text delta is broadcast to all views of that buffer.
690    pub fn execute(
691        &mut self,
692        view_id: ViewId,
693        command: Command,
694    ) -> Result<CommandResult, WorkspaceError> {
695        let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
696            return Err(WorkspaceError::ViewNotFound(view_id));
697        };
698
699        let change_type = Self::command_change_type(&command);
700        if change_type.is_none() {
701            // Still run command because it may validate (e.g. ScrollTo), but treat as no version bump.
702        }
703
704        // Borrow maps separately so we can mutably access a view and its buffer.
705        let views = &mut self.views;
706        let buffers = &mut self.buffers;
707
708        let Some(view) = views.get_mut(&view_id) else {
709            return Err(WorkspaceError::ViewNotFound(view_id));
710        };
711        let Some(buffer) = buffers.get_mut(&buffer_id) else {
712            return Err(WorkspaceError::BufferNotFound(buffer_id));
713        };
714
715        let before_view_core = view.core.clone();
716        let before_line_index = buffer.executor.editor().line_index.clone();
717        let before_char_count = buffer.executor.editor().char_count();
718
719        // Load view-local state into the executor, execute, then snapshot it back.
720        view.core.apply_to_executor(&mut buffer.executor);
721
722        let result = buffer.executor.execute(command.clone()).map_err(|err| {
723            WorkspaceError::CommandFailed {
724                view: view_id,
725                message: err.to_string(),
726            }
727        })?;
728
729        view.core = ViewCore::from_executor(&buffer.executor);
730
731        let delta = buffer.executor.take_last_text_delta().map(Arc::new);
732        let after_char_count = buffer.executor.editor().char_count();
733
734        // Detect no-ops: successful execution but no meaningful state change.
735        let view_changed = view.core != before_view_core;
736        let buffer_text_changed = delta.is_some()
737            // `Backspace`/`DeleteForward` can succeed as boundary no-ops; detect via char count.
738            || after_char_count != before_char_count;
739
740        let buffer_derived_changed = matches!(command, Command::Style(_));
741
742        if !(view_changed || buffer_text_changed || buffer_derived_changed) {
743            return Ok(result);
744        }
745
746        let change_type = if buffer_text_changed {
747            StateChangeType::DocumentModified
748        } else {
749            change_type.unwrap_or(StateChangeType::ViewportChanged)
750        };
751
752        if buffer_text_changed || buffer_derived_changed {
753            // Broadcast to all views of this buffer.
754            let delta_arc = delta.clone();
755            if let Some(delta_arc) = delta_arc {
756                buffer.last_text_delta = Some(delta_arc.clone());
757                for other in views.values_mut() {
758                    if other.buffer != buffer_id {
759                        continue;
760                    }
761                    other.last_text_delta = Some(delta_arc.clone());
762                }
763            } else {
764                buffer.last_text_delta = None;
765            }
766
767            // Shift other views' cursor/selections through the delta (if any).
768            if let Some(ref delta_arc) = delta {
769                let new_index = &buffer.executor.editor().line_index;
770                for (other_id, other) in views.iter_mut() {
771                    if other.buffer != buffer_id || *other_id == view_id {
772                        continue;
773                    }
774
775                    other.core.cursor_position = apply_position_delta(
776                        &before_line_index,
777                        new_index,
778                        other.core.cursor_position,
779                        delta_arc,
780                    );
781
782                    if let Some(ref sel) = other.core.selection {
783                        other.core.selection = Some(apply_selection_delta(
784                            &before_line_index,
785                            new_index,
786                            sel,
787                            delta_arc,
788                        ));
789                    }
790
791                    for sel in &mut other.core.secondary_selections {
792                        *sel = apply_selection_delta(&before_line_index, new_index, sel, delta_arc);
793                    }
794                }
795            }
796
797            for other in views.values_mut() {
798                if other.buffer != buffer_id {
799                    continue;
800                }
801                Self::notify_view(other, change_type, delta.clone());
802            }
803
804            buffer.version = buffer.version.saturating_add(1);
805        } else {
806            Self::notify_view(view, change_type, None);
807        }
808
809        Ok(result)
810    }
811
812    /// Set the viewport height for a view (used for `ViewportState` calculations).
813    pub fn set_viewport_height(
814        &mut self,
815        view_id: ViewId,
816        height: usize,
817    ) -> Result<(), WorkspaceError> {
818        let Some(view) = self.views.get_mut(&view_id) else {
819            return Err(WorkspaceError::ViewNotFound(view_id));
820        };
821        view.viewport_height = Some(height);
822        Ok(())
823    }
824
825    /// Set the scroll position (top visual row) for a view.
826    pub fn set_scroll_top(
827        &mut self,
828        view_id: ViewId,
829        scroll_top: usize,
830    ) -> Result<(), WorkspaceError> {
831        let Some(view) = self.views.get_mut(&view_id) else {
832            return Err(WorkspaceError::ViewNotFound(view_id));
833        };
834        view.scroll_top = scroll_top;
835        Ok(())
836    }
837
838    /// Set sub-row smooth-scroll offset for a view.
839    pub fn set_scroll_sub_row_offset(
840        &mut self,
841        view_id: ViewId,
842        sub_row_offset: u16,
843    ) -> Result<(), WorkspaceError> {
844        let Some(view) = self.views.get_mut(&view_id) else {
845            return Err(WorkspaceError::ViewNotFound(view_id));
846        };
847        view.scroll_sub_row_offset = sub_row_offset;
848        Ok(())
849    }
850
851    /// Set overscan rows for a view.
852    pub fn set_overscan_rows(
853        &mut self,
854        view_id: ViewId,
855        overscan_rows: usize,
856    ) -> Result<(), WorkspaceError> {
857        let Some(view) = self.views.get_mut(&view_id) else {
858            return Err(WorkspaceError::ViewNotFound(view_id));
859        };
860        view.overscan_rows = overscan_rows;
861        Ok(())
862    }
863
864    /// Set smooth-scroll state for a view.
865    pub fn set_smooth_scroll_state(
866        &mut self,
867        view_id: ViewId,
868        state: ViewSmoothScrollState,
869    ) -> Result<(), WorkspaceError> {
870        let Some(view) = self.views.get_mut(&view_id) else {
871            return Err(WorkspaceError::ViewNotFound(view_id));
872        };
873        view.scroll_top = state.top_visual_row;
874        view.scroll_sub_row_offset = state.sub_row_offset;
875        view.overscan_rows = state.overscan_rows;
876        Ok(())
877    }
878
879    /// Get viewport state for a view, including total visual lines and overscan prefetch range.
880    pub fn viewport_state_for_view(
881        &mut self,
882        view_id: ViewId,
883    ) -> Result<WorkspaceViewportState, WorkspaceError> {
884        let Some((
885            buffer_id,
886            view_core,
887            scroll_top,
888            viewport_height,
889            sub_row_offset,
890            overscan_rows,
891        )) = self.views.get(&view_id).map(|v| {
892            (
893                v.buffer,
894                v.core.clone(),
895                v.scroll_top,
896                v.viewport_height,
897                v.scroll_sub_row_offset,
898                v.overscan_rows,
899            )
900        })
901        else {
902            return Err(WorkspaceError::ViewNotFound(view_id));
903        };
904
905        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
906            return Err(WorkspaceError::BufferNotFound(buffer_id));
907        };
908        view_core.apply_to_executor(&mut buffer.executor);
909        let editor = buffer.executor.editor();
910
911        let total_visual_lines = editor.visual_line_count();
912        let visible_end = if let Some(height) = viewport_height {
913            scroll_top.saturating_add(height).min(total_visual_lines)
914        } else {
915            total_visual_lines
916        };
917        let visible_lines = scroll_top.min(total_visual_lines)..visible_end;
918        let prefetch_start = visible_lines.start.saturating_sub(overscan_rows);
919        let prefetch_end = visible_lines
920            .end
921            .saturating_add(overscan_rows)
922            .min(total_visual_lines);
923
924        Ok(WorkspaceViewportState {
925            width: editor.viewport_width,
926            height: viewport_height,
927            scroll_top,
928            visible_lines,
929            total_visual_lines,
930            smooth_scroll: ViewSmoothScrollState {
931                top_visual_row: scroll_top,
932                sub_row_offset,
933                overscan_rows,
934            },
935            prefetch_lines: prefetch_start..prefetch_end,
936        })
937    }
938
939    /// Get total visual lines for a view (wrap + folding aware).
940    pub fn total_visual_lines_for_view(
941        &mut self,
942        view_id: ViewId,
943    ) -> Result<usize, WorkspaceError> {
944        Ok(self.viewport_state_for_view(view_id)?.total_visual_lines)
945    }
946
947    /// Map global visual row to `(logical_line, visual_in_logical)` for a view.
948    pub fn visual_to_logical_for_view(
949        &mut self,
950        view_id: ViewId,
951        visual_row: usize,
952    ) -> Result<(usize, usize), WorkspaceError> {
953        let Some((buffer_id, view_core)) =
954            self.views.get(&view_id).map(|v| (v.buffer, v.core.clone()))
955        else {
956            return Err(WorkspaceError::ViewNotFound(view_id));
957        };
958        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
959            return Err(WorkspaceError::BufferNotFound(buffer_id));
960        };
961        view_core.apply_to_executor(&mut buffer.executor);
962        Ok(buffer.executor.editor().visual_to_logical_line(visual_row))
963    }
964
965    /// Map logical position to global visual `(row, x_cells)` for a view.
966    pub fn logical_to_visual_for_view(
967        &mut self,
968        view_id: ViewId,
969        line: usize,
970        column: usize,
971    ) -> Result<Option<(usize, usize)>, WorkspaceError> {
972        let Some((buffer_id, view_core)) =
973            self.views.get(&view_id).map(|v| (v.buffer, v.core.clone()))
974        else {
975            return Err(WorkspaceError::ViewNotFound(view_id));
976        };
977        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
978            return Err(WorkspaceError::BufferNotFound(buffer_id));
979        };
980        view_core.apply_to_executor(&mut buffer.executor);
981        Ok(buffer
982            .executor
983            .editor()
984            .logical_position_to_visual(line, column))
985    }
986
987    /// Map visual `(row, x_cells)` back to logical position for a view.
988    pub fn visual_position_to_logical_for_view(
989        &mut self,
990        view_id: ViewId,
991        visual_row: usize,
992        x_cells: usize,
993    ) -> Result<Option<Position>, WorkspaceError> {
994        let Some((buffer_id, view_core)) =
995            self.views.get(&view_id).map(|v| (v.buffer, v.core.clone()))
996        else {
997            return Err(WorkspaceError::ViewNotFound(view_id));
998        };
999        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
1000            return Err(WorkspaceError::BufferNotFound(buffer_id));
1001        };
1002        view_core.apply_to_executor(&mut buffer.executor);
1003        Ok(buffer
1004            .executor
1005            .editor()
1006            .visual_position_to_logical(visual_row, x_cells))
1007    }
1008
1009    /// Get the full document text for a buffer.
1010    pub fn buffer_text(&self, buffer_id: BufferId) -> Result<String, WorkspaceError> {
1011        let Some(buffer) = self.buffers.get(&buffer_id) else {
1012            return Err(WorkspaceError::BufferNotFound(buffer_id));
1013        };
1014        Ok(buffer.executor.editor().get_text())
1015    }
1016
1017    /// Get styled viewport content for a view (by visual line).
1018    pub fn get_viewport_content_styled(
1019        &mut self,
1020        view_id: ViewId,
1021        start_visual_row: usize,
1022        count: usize,
1023    ) -> Result<crate::HeadlessGrid, WorkspaceError> {
1024        let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
1025            return Err(WorkspaceError::ViewNotFound(view_id));
1026        };
1027
1028        let view_core = self
1029            .views
1030            .get(&view_id)
1031            .map(|v| v.core.clone())
1032            .ok_or(WorkspaceError::ViewNotFound(view_id))?;
1033
1034        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
1035            return Err(WorkspaceError::BufferNotFound(buffer_id));
1036        };
1037
1038        view_core.apply_to_executor(&mut buffer.executor);
1039        Ok(buffer
1040            .executor
1041            .editor()
1042            .get_headless_grid_styled(start_visual_row, count))
1043    }
1044
1045    /// Get lightweight minimap content for a view (by visual line).
1046    pub fn get_minimap_content(
1047        &mut self,
1048        view_id: ViewId,
1049        start_visual_row: usize,
1050        count: usize,
1051    ) -> Result<crate::MinimapGrid, WorkspaceError> {
1052        let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
1053            return Err(WorkspaceError::ViewNotFound(view_id));
1054        };
1055
1056        let view_core = self
1057            .views
1058            .get(&view_id)
1059            .map(|v| v.core.clone())
1060            .ok_or(WorkspaceError::ViewNotFound(view_id))?;
1061
1062        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
1063            return Err(WorkspaceError::BufferNotFound(buffer_id));
1064        };
1065
1066        view_core.apply_to_executor(&mut buffer.executor);
1067        Ok(buffer
1068            .executor
1069            .editor()
1070            .get_minimap_grid(start_visual_row, count))
1071    }
1072
1073    /// Get a decoration-aware composed viewport snapshot for a view (by composed visual line).
1074    ///
1075    /// This snapshot can include virtual text (inlay hints, code lens) injected from the buffer's
1076    /// decoration layers. See [`crate::EditorCore::get_headless_grid_composed`] for details.
1077    pub fn get_viewport_content_composed(
1078        &mut self,
1079        view_id: ViewId,
1080        start_visual_row: usize,
1081        count: usize,
1082    ) -> Result<crate::ComposedGrid, WorkspaceError> {
1083        let Some(buffer_id) = self.views.get(&view_id).map(|v| v.buffer) else {
1084            return Err(WorkspaceError::ViewNotFound(view_id));
1085        };
1086
1087        let view_core = self
1088            .views
1089            .get(&view_id)
1090            .map(|v| v.core.clone())
1091            .ok_or(WorkspaceError::ViewNotFound(view_id))?;
1092
1093        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
1094            return Err(WorkspaceError::BufferNotFound(buffer_id));
1095        };
1096
1097        view_core.apply_to_executor(&mut buffer.executor);
1098        Ok(buffer
1099            .executor
1100            .editor()
1101            .get_headless_grid_composed(start_visual_row, count))
1102    }
1103
1104    /// Apply derived-state edits to a buffer and broadcast them to all views of that buffer.
1105    pub fn apply_processing_edits<I>(
1106        &mut self,
1107        buffer_id: BufferId,
1108        edits: I,
1109    ) -> Result<(), WorkspaceError>
1110    where
1111        I: IntoIterator<Item = ProcessingEdit>,
1112    {
1113        let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
1114            return Err(WorkspaceError::BufferNotFound(buffer_id));
1115        };
1116
1117        let mut style_changed = false;
1118        let mut folding_changed = false;
1119        let mut diagnostics_changed = false;
1120        let mut decorations_changed = false;
1121        let mut symbols_changed = false;
1122
1123        for edit in edits {
1124            match edit {
1125                ProcessingEdit::ReplaceStyleLayer { layer, intervals } => {
1126                    let editor = buffer.executor.editor_mut();
1127                    if intervals.is_empty() {
1128                        editor.style_layers.remove(&layer);
1129                    } else {
1130                        let tree = editor.style_layers.entry(layer).or_default();
1131                        tree.clear();
1132                        for interval in intervals {
1133                            if interval.start < interval.end {
1134                                tree.insert(interval);
1135                            }
1136                        }
1137                    }
1138                    style_changed = true;
1139                }
1140                ProcessingEdit::ClearStyleLayer { layer } => {
1141                    buffer.executor.editor_mut().style_layers.remove(&layer);
1142                    style_changed = true;
1143                }
1144                ProcessingEdit::ReplaceFoldingRegions {
1145                    mut regions,
1146                    preserve_collapsed,
1147                } => {
1148                    if preserve_collapsed {
1149                        let collapsed: HashSet<(usize, usize)> = buffer
1150                            .executor
1151                            .editor()
1152                            .folding_manager
1153                            .derived_regions()
1154                            .iter()
1155                            .filter(|r| r.is_collapsed)
1156                            .map(|r| (r.start_line, r.end_line))
1157                            .collect();
1158
1159                        for region in &mut regions {
1160                            if collapsed.contains(&(region.start_line, region.end_line)) {
1161                                region.is_collapsed = true;
1162                            }
1163                        }
1164                    }
1165
1166                    buffer
1167                        .executor
1168                        .editor_mut()
1169                        .folding_manager
1170                        .replace_derived_regions(regions);
1171                    buffer
1172                        .executor
1173                        .editor_mut()
1174                        .invalidate_visual_row_index_cache();
1175                    folding_changed = true;
1176                }
1177                ProcessingEdit::ClearFoldingRegions => {
1178                    buffer
1179                        .executor
1180                        .editor_mut()
1181                        .folding_manager
1182                        .clear_derived_regions();
1183                    buffer
1184                        .executor
1185                        .editor_mut()
1186                        .invalidate_visual_row_index_cache();
1187                    folding_changed = true;
1188                }
1189                ProcessingEdit::ReplaceDiagnostics { diagnostics } => {
1190                    buffer.executor.editor_mut().diagnostics = diagnostics;
1191                    diagnostics_changed = true;
1192                }
1193                ProcessingEdit::ClearDiagnostics => {
1194                    buffer.executor.editor_mut().diagnostics.clear();
1195                    diagnostics_changed = true;
1196                }
1197                ProcessingEdit::ReplaceDecorations {
1198                    layer,
1199                    mut decorations,
1200                } => {
1201                    decorations.sort_unstable_by_key(|d| (d.range.start, d.range.end));
1202                    buffer
1203                        .executor
1204                        .editor_mut()
1205                        .decorations
1206                        .insert(layer, decorations);
1207                    decorations_changed = true;
1208                }
1209                ProcessingEdit::ClearDecorations { layer } => {
1210                    buffer.executor.editor_mut().decorations.remove(&layer);
1211                    decorations_changed = true;
1212                }
1213                ProcessingEdit::ReplaceDocumentSymbols { symbols } => {
1214                    buffer.executor.editor_mut().document_symbols = symbols;
1215                    symbols_changed = true;
1216                }
1217                ProcessingEdit::ClearDocumentSymbols => {
1218                    buffer.executor.editor_mut().document_symbols =
1219                        crate::DocumentOutline::default();
1220                    symbols_changed = true;
1221                }
1222            }
1223        }
1224
1225        let change_type = if folding_changed {
1226            Some(StateChangeType::FoldingChanged)
1227        } else if style_changed {
1228            Some(StateChangeType::StyleChanged)
1229        } else if decorations_changed {
1230            Some(StateChangeType::DecorationsChanged)
1231        } else if diagnostics_changed {
1232            Some(StateChangeType::DiagnosticsChanged)
1233        } else if symbols_changed {
1234            Some(StateChangeType::SymbolsChanged)
1235        } else {
1236            None
1237        };
1238
1239        if let Some(change_type) = change_type {
1240            for view in self.views.values_mut() {
1241                if view.buffer == buffer_id {
1242                    Self::notify_view(view, change_type, None);
1243                }
1244            }
1245            buffer.version = buffer.version.saturating_add(1);
1246        }
1247
1248        Ok(())
1249    }
1250
1251    /// Search across all open buffers in the workspace.
1252    ///
1253    /// - This is purely in-memory (no file I/O).
1254    /// - Match ranges are returned as **character offsets** (half-open).
1255    pub fn search_all_open_buffers(
1256        &self,
1257        query: &str,
1258        options: SearchOptions,
1259    ) -> Result<Vec<WorkspaceSearchResult>, SearchError> {
1260        let mut out: Vec<WorkspaceSearchResult> = Vec::new();
1261
1262        for (id, entry) in &self.buffers {
1263            let text = entry.executor.editor().get_text();
1264            let matches = find_all(&text, query, options)?;
1265            if matches.is_empty() {
1266                continue;
1267            }
1268
1269            out.push(WorkspaceSearchResult {
1270                id: *id,
1271                uri: entry.meta.uri.clone(),
1272                matches,
1273            });
1274        }
1275
1276        Ok(out)
1277    }
1278
1279    /// Apply a set of text edits to multiple open buffers.
1280    ///
1281    /// - This is purely in-memory (no file I/O).
1282    /// - Edits are applied as a single undoable step **per buffer**.
1283    /// - Buffers are applied in deterministic `BufferId` order.
1284    pub fn apply_text_edits<I>(
1285        &mut self,
1286        edits: I,
1287    ) -> Result<Vec<(BufferId, usize)>, WorkspaceError>
1288    where
1289        I: IntoIterator<Item = (BufferId, Vec<TextEditSpec>)>,
1290    {
1291        let mut by_id: BTreeMap<BufferId, Vec<TextEditSpec>> = BTreeMap::new();
1292        for (id, mut buffer_edits) in edits {
1293            by_id.entry(id).or_default().append(&mut buffer_edits);
1294        }
1295
1296        let mut applied: Vec<(BufferId, usize)> = Vec::new();
1297        for (buffer_id, buffer_edits) in by_id {
1298            let edit_count = buffer_edits.len();
1299            if edit_count == 0 {
1300                continue;
1301            }
1302
1303            let Some(buffer) = self.buffers.get_mut(&buffer_id) else {
1304                return Err(WorkspaceError::BufferNotFound(buffer_id));
1305            };
1306
1307            let before_line_index = buffer.executor.editor().line_index.clone();
1308            let before_char_count = buffer.executor.editor().char_count();
1309
1310            // Apply without relying on any specific view selection: load a neutral view state.
1311            let neutral = ViewCore {
1312                cursor_position: Position::new(0, 0),
1313                selection: None,
1314                secondary_selections: Vec::new(),
1315                viewport_width: buffer.executor.editor().viewport_width.max(1),
1316                wrap_mode: buffer.executor.editor().layout_engine.wrap_mode(),
1317                wrap_indent: buffer.executor.editor().layout_engine.wrap_indent(),
1318                tab_width: buffer.executor.editor().layout_engine.tab_width(),
1319                tab_key_behavior: buffer.executor.tab_key_behavior(),
1320                preferred_x_cells: None,
1321            };
1322            neutral.apply_to_executor(&mut buffer.executor);
1323
1324            buffer
1325                .executor
1326                .execute(Command::Edit(EditCommand::ApplyTextEdits {
1327                    edits: buffer_edits,
1328                }))
1329                .map_err(|err| WorkspaceError::ApplyEditsFailed {
1330                    buffer: buffer_id,
1331                    message: err.to_string(),
1332                })?;
1333
1334            let delta = buffer.executor.take_last_text_delta().map(Arc::new);
1335            let after_char_count = buffer.executor.editor().char_count();
1336            let changed = delta.is_some() || after_char_count != before_char_count;
1337
1338            if changed {
1339                if let Some(ref delta_arc) = delta {
1340                    buffer.last_text_delta = Some(delta_arc.clone());
1341                    let new_index = &buffer.executor.editor().line_index;
1342                    for view in self.views.values_mut() {
1343                        if view.buffer != buffer_id {
1344                            continue;
1345                        }
1346
1347                        view.last_text_delta = Some(delta_arc.clone());
1348
1349                        view.core.cursor_position = apply_position_delta(
1350                            &before_line_index,
1351                            new_index,
1352                            view.core.cursor_position,
1353                            delta_arc,
1354                        );
1355                        if let Some(ref sel) = view.core.selection {
1356                            view.core.selection = Some(apply_selection_delta(
1357                                &before_line_index,
1358                                new_index,
1359                                sel,
1360                                delta_arc,
1361                            ));
1362                        }
1363                        for sel in &mut view.core.secondary_selections {
1364                            *sel = apply_selection_delta(
1365                                &before_line_index,
1366                                new_index,
1367                                sel,
1368                                delta_arc,
1369                            );
1370                        }
1371
1372                        Self::notify_view(
1373                            view,
1374                            StateChangeType::DocumentModified,
1375                            Some(delta_arc.clone()),
1376                        );
1377                    }
1378                } else {
1379                    buffer.last_text_delta = None;
1380                    for view in self.views.values_mut() {
1381                        if view.buffer == buffer_id {
1382                            Self::notify_view(view, StateChangeType::DocumentModified, None);
1383                        }
1384                    }
1385                }
1386
1387                buffer.version = buffer.version.saturating_add(1);
1388            }
1389
1390            applied.push((buffer_id, edit_count));
1391        }
1392
1393        Ok(applied)
1394    }
1395}