Skip to content

Commit 48b8c73

Browse files
Draft the editor selection persistence
1 parent 510260a commit 48b8c73

File tree

4 files changed

+165
-3
lines changed

4 files changed

+165
-3
lines changed

crates/editor/src/editor.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ use language::{
106106
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
107107
use linked_editing_ranges::refresh_linked_ranges;
108108
use mouse_context_menu::MouseContextMenu;
109+
use persistence::DB;
109110
pub use proposed_changes_editor::{
110111
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
111112
};
@@ -171,8 +172,11 @@ use ui::{
171172
Tooltip,
172173
};
173174
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
174-
use workspace::item::{ItemHandle, PreviewTabsSettings};
175175
use workspace::notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt};
176+
use workspace::{
177+
item::{ItemHandle, PreviewTabsSettings},
178+
ItemId,
179+
};
176180
use workspace::{
177181
searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
178182
};
@@ -761,6 +765,7 @@ pub struct Editor {
761765
selection_mark_mode: bool,
762766
toggle_fold_multiple_buffers: Task<()>,
763767
_scroll_cursor_center_top_bottom_task: Task<()>,
768+
serialize_selections: Task<()>,
764769
}
765770

766771
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1472,6 +1477,7 @@ impl Editor {
14721477
_scroll_cursor_center_top_bottom_task: Task::ready(()),
14731478
selection_mark_mode: false,
14741479
toggle_fold_multiple_buffers: Task::ready(()),
1480+
serialize_selections: Task::ready(()),
14751481
text_style_refinement: None,
14761482
load_diff_task: load_uncommitted_diff,
14771483
};
@@ -2177,9 +2183,34 @@ impl Editor {
21772183
self.blink_manager.update(cx, BlinkManager::pause_blinking);
21782184
cx.emit(EditorEvent::SelectionsChanged { local });
21792185

2180-
if self.selections.disjoint_anchors().len() == 1 {
2186+
let selections = self.selections.disjoint.clone();
2187+
if selections.len() == 1 {
21812188
cx.emit(SearchEvent::ActiveMatchChanged)
21822189
}
2190+
if local {
2191+
if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
2192+
let background_executor = cx.background_executor().clone();
2193+
let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2194+
let snapshot = self.buffer().read(cx).snapshot(cx);
2195+
self.serialize_selections = cx.background_spawn(async move {
2196+
background_executor.timer(UPDATE_DEBOUNCE).await;
2197+
let selections = selections
2198+
.iter()
2199+
.map(|selection| {
2200+
(
2201+
selection.start.to_offset(&snapshot),
2202+
selection.end.to_offset(&snapshot),
2203+
)
2204+
})
2205+
.collect();
2206+
DB.save_editor_selections(editor_id, workspace_id, selections)
2207+
.await
2208+
.context("persisting editor selections")
2209+
.log_err();
2210+
});
2211+
}
2212+
}
2213+
21832214
cx.notify();
21842215
}
21852216

@@ -2193,7 +2224,7 @@ impl Editor {
21932224
self.change_selections_inner(autoscroll, true, window, cx, change)
21942225
}
21952226

2196-
pub fn change_selections_inner<R>(
2227+
fn change_selections_inner<R>(
21972228
&mut self,
21982229
autoscroll: Option<Autoscroll>,
21992230
request_completions: bool,
@@ -14874,6 +14905,23 @@ impl Editor {
1487414905
pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
1487514906
self.load_diff_task.clone()
1487614907
}
14908+
14909+
// TODO kb allow to turn it off in the settings
14910+
fn read_selections_from_db(
14911+
&mut self,
14912+
item_id: u64,
14913+
workspace_id: WorkspaceId,
14914+
window: &mut Window,
14915+
cx: &mut Context<Editor>,
14916+
) {
14917+
let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() else {
14918+
return;
14919+
};
14920+
14921+
self.change_selections(None, window, cx, |s| {
14922+
s.select_ranges(selections.into_iter().map(|(start, end)| start..end));
14923+
});
14924+
}
1487714925
}
1487814926

1487914927
fn get_uncommitted_diff_for_buffer(

crates/editor/src/items.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,7 @@ impl SerializableItem for Editor {
10751075
cx.new(|cx| {
10761076
let mut editor = Editor::for_buffer(buffer, Some(project), window, cx);
10771077

1078+
editor.read_selections_from_db(item_id, workspace_id, window, cx);
10781079
editor.read_scroll_position_from_db(item_id, workspace_id, window, cx);
10791080
editor
10801081
})
@@ -1122,6 +1123,7 @@ impl SerializableItem for Editor {
11221123
);
11231124
}
11241125
buffer.set_text(buffer_text, cx);
1126+
// buffer.set_active_selections(selections, line_mode, cursor_shape, cx);
11251127
})?;
11261128
}
11271129

@@ -1130,6 +1132,12 @@ impl SerializableItem for Editor {
11301132
let mut editor =
11311133
Editor::for_buffer(buffer, Some(project), window, cx);
11321134

1135+
editor.read_selections_from_db(
1136+
item_id,
1137+
workspace_id,
1138+
window,
1139+
cx,
1140+
);
11331141
editor.read_scroll_position_from_db(
11341142
item_id,
11351143
workspace_id,
@@ -1148,6 +1156,7 @@ impl SerializableItem for Editor {
11481156
window.spawn(cx, |mut cx| async move {
11491157
let editor = open_by_abs_path?.await?.downcast::<Editor>().with_context(|| format!("Failed to downcast to Editor after opening abs path {abs_path:?}"))?;
11501158
editor.update_in(&mut cx, |editor, window, cx| {
1159+
editor.read_selections_from_db(item_id, workspace_id, window, cx);
11511160
editor.read_scroll_position_from_db(item_id, workspace_id, window, cx);
11521161
})?;
11531162
Ok(editor)

crates/editor/src/persistence.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::Result;
22
use db::sqlez::bindable::{Bind, Column, StaticColumnCount};
33
use db::sqlez::statement::Statement;
44
use fs::MTime;
5+
use itertools::Itertools as _;
56
use std::path::PathBuf;
67

78
use db::sqlez_macros::sql;
@@ -134,9 +135,25 @@ define_connection!(
134135
ALTER TABLE editors ADD COLUMN mtime_seconds INTEGER DEFAULT NULL;
135136
ALTER TABLE editors ADD COLUMN mtime_nanos INTEGER DEFAULT NULL;
136137
),
138+
sql! (
139+
CREATE TABLE editor_selections (
140+
item_id INTEGER NOT NULL,
141+
editor_id INTEGER NOT NULL,
142+
workspace_id INTEGER NOT NULL,
143+
start INTEGER NOT NULL,
144+
end INTEGER NOT NULL,
145+
PRIMARY KEY(item_id),
146+
FOREIGN KEY(editor_id, workspace_id) REFERENCES editors(item_id, workspace_id)
147+
) STRICT;
148+
),
137149
];
138150
);
139151

152+
// https://www.sqlite.org/limits.html
153+
// > .. the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER,
154+
// > which defaults to <..> 32766 for SQLite versions after 3.32.0.
155+
const MAX_QUERY_PLACEHOLDERS: usize = 32000;
156+
140157
impl EditorDb {
141158
query! {
142159
pub fn get_serialized_editor(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<SerializedEditor>> {
@@ -188,6 +205,75 @@ impl EditorDb {
188205
}
189206
}
190207

208+
query! {
209+
pub fn get_editor_selections(
210+
editor_id: ItemId,
211+
workspace_id: WorkspaceId
212+
) -> Result<Vec<(usize, usize)>> {
213+
SELECT start, end
214+
FROM selections
215+
WHERE item_id = ?1 AND workspace_id = ?2
216+
}
217+
}
218+
219+
pub async fn save_editor_selections(
220+
&self,
221+
editor_id: ItemId,
222+
workspace_id: WorkspaceId,
223+
selections: Vec<(usize, usize)>,
224+
) -> Result<()> {
225+
let mut first_selection;
226+
let mut last_selection = 0_usize;
227+
for (count, placeholders) in std::iter::once("(?, ?, ?, ?)")
228+
.cycle()
229+
.take(selections.len())
230+
.chunks(MAX_QUERY_PLACEHOLDERS / 4)
231+
.into_iter()
232+
.map(|chunk| {
233+
let mut count = 0;
234+
let placeholders = chunk
235+
.inspect(|_| {
236+
count += 1;
237+
})
238+
.join(", ");
239+
(count, placeholders)
240+
})
241+
.collect::<Vec<_>>()
242+
{
243+
first_selection = last_selection;
244+
last_selection = last_selection + count;
245+
let query = format!(
246+
r#"
247+
BEGIN TRANSACTION;
248+
249+
DELETE FROM editor_selections WHERE item_id = ?1 AND workspace_id = ?2;
250+
251+
INSERT INTO selections (item_id, workspace_id, start, end)
252+
VALUES ({placeholders});
253+
254+
COMMIT;
255+
256+
DELETE FROM editors WHERE workspace_id = ? AND item_id NOT IN ({placeholders})"#
257+
);
258+
259+
let selections = selections[first_selection..last_selection].to_vec();
260+
self.write(move |conn| {
261+
let mut statement = Statement::prepare(conn, query)?;
262+
statement.bind(&editor_id, 1)?;
263+
let mut next_index = statement.bind(&workspace_id, 2)?;
264+
for (start, end) in selections {
265+
next_index = statement.bind(&editor_id, next_index)?;
266+
next_index = statement.bind(&workspace_id, next_index)?;
267+
next_index = statement.bind(&start, next_index)?;
268+
next_index = statement.bind(&end, next_index)?;
269+
}
270+
statement.exec()
271+
})
272+
.await?;
273+
}
274+
Ok(())
275+
}
276+
191277
pub async fn delete_unloaded_items(
192278
&self,
193279
workspace: WorkspaceId,

crates/language/src/buffer.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4032,6 +4032,25 @@ impl BufferSnapshot {
40324032
})
40334033
}
40344034

4035+
/// Returns all selections of the current user.
4036+
#[allow(clippy::type_complexity)]
4037+
pub fn local_selections(
4038+
&self,
4039+
) -> impl Iterator<
4040+
Item = (
4041+
bool,
4042+
CursorShape,
4043+
impl Iterator<Item = &Selection<Anchor>> + '_,
4044+
),
4045+
> + '_ {
4046+
self.remote_selections
4047+
.iter()
4048+
.filter(move |(replica_id, set)| {
4049+
(**replica_id == self.text.replica_id()) && !set.selections.is_empty()
4050+
})
4051+
.map(move |(_, set)| (set.line_mode, set.cursor_shape, set.selections.iter()))
4052+
}
4053+
40354054
/// Returns if the buffer contains any diagnostics.
40364055
pub fn has_diagnostics(&self) -> bool {
40374056
!self.diagnostics.is_empty()

0 commit comments

Comments
 (0)