Skip to content

Commit e12d760

Browse files
committed
자동 포멧팅 기능 개선
1 parent 46f7e6b commit e12d760

7 files changed

Lines changed: 555 additions & 135 deletions

File tree

src/ui/sql_editor/chunked_text.rs

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ impl TextChunk {
3030
/// by the edited text and the number of chunks rather than the document tail.
3131
#[derive(Clone, Debug, Default, PartialEq, Eq)]
3232
pub(crate) struct ChunkedText {
33-
chunks: Vec<TextChunk>,
33+
chunks: Arc<Vec<TextChunk>>,
34+
chunk_ends: Arc<Vec<usize>>,
35+
line_break_ends: Arc<Vec<usize>>,
3436
len: usize,
3537
line_break_count: usize,
3638
}
@@ -43,8 +45,11 @@ impl ChunkedText {
4345
pub(crate) fn from_str(text: &str) -> Self {
4446
let chunks = split_text_chunks(text);
4547
let line_break_count = chunks.iter().map(|chunk| chunk.line_breaks.len()).sum();
48+
let (chunk_ends, line_break_ends) = chunk_indexes(&chunks);
4649
Self {
47-
chunks,
50+
chunks: Arc::new(chunks),
51+
chunk_ends: Arc::new(chunk_ends),
52+
line_break_ends: Arc::new(line_break_ends),
4853
len: text.len(),
4954
line_break_count,
5055
}
@@ -85,7 +90,7 @@ impl ChunkedText {
8590

8691
pub(crate) fn to_flat_string(&self) -> String {
8792
let mut text = String::with_capacity(self.len);
88-
for chunk in &self.chunks {
93+
for chunk in self.chunks.iter() {
8994
text.push_str(&chunk.text);
9095
}
9196
text
@@ -125,13 +130,14 @@ impl ChunkedText {
125130
return Some(String::new());
126131
}
127132
let mut result = String::with_capacity(end.saturating_sub(start));
128-
let mut chunk_start = 0usize;
129-
for chunk in &self.chunks {
133+
let (start_idx, _) = self.locate(start);
134+
let mut chunk_start = start_idx
135+
.checked_sub(1)
136+
.and_then(|idx| self.chunk_ends.get(idx))
137+
.copied()
138+
.unwrap_or(0);
139+
for chunk in self.chunks.iter().skip(start_idx) {
130140
let chunk_end = chunk_start.saturating_add(chunk.len());
131-
if chunk_end <= start {
132-
chunk_start = chunk_end;
133-
continue;
134-
}
135141
if chunk_start >= end {
136142
break;
137143
}
@@ -190,8 +196,11 @@ impl ChunkedText {
190196
.iter()
191197
.map(|chunk| chunk.line_breaks.len())
192198
.sum::<usize>();
193-
self.chunks
199+
Arc::make_mut(&mut self.chunks)
194200
.splice(drain_start..drain_end.max(drain_start), replacement_chunks);
201+
let (chunk_ends, line_break_ends) = chunk_indexes(&self.chunks);
202+
self.chunk_ends = Arc::new(chunk_ends);
203+
self.line_break_ends = Arc::new(line_break_ends);
195204
self.len = self
196205
.len
197206
.saturating_sub(end.saturating_sub(start))
@@ -213,19 +222,18 @@ impl ChunkedText {
213222

214223
pub(crate) fn line_index_for_position(&self, pos: usize) -> usize {
215224
let pos = pos.min(self.len);
216-
let mut absolute = 0usize;
217-
let mut line_index = 0usize;
218-
for chunk in &self.chunks {
219-
let chunk_end = absolute.saturating_add(chunk.len());
220-
if pos < chunk_end {
221-
let relative = pos.saturating_sub(absolute);
222-
line_index += chunk.line_breaks.partition_point(|value| *value < relative);
223-
return line_index;
224-
}
225-
line_index = line_index.saturating_add(chunk.line_breaks.len());
226-
absolute = chunk_end;
227-
}
228-
line_index
225+
let (chunk_idx, relative) = self.locate(pos);
226+
let preceding_breaks = chunk_idx
227+
.checked_sub(1)
228+
.and_then(|idx| self.line_break_ends.get(idx))
229+
.copied()
230+
.unwrap_or(0);
231+
self.chunks
232+
.get(chunk_idx)
233+
.map_or(preceding_breaks, |chunk| {
234+
preceding_breaks
235+
.saturating_add(chunk.line_breaks.partition_point(|value| *value < relative))
236+
})
229237
}
230238

231239
pub(crate) fn line_start(&self, pos: usize) -> usize {
@@ -252,35 +260,34 @@ impl ChunkedText {
252260
.unwrap_or(self.len)
253261
}
254262

255-
fn nth_line_break(&self, mut ordinal: usize) -> Option<usize> {
256-
let mut absolute = 0usize;
257-
for chunk in &self.chunks {
258-
if ordinal < chunk.line_breaks.len() {
259-
return chunk
260-
.line_breaks
261-
.get(ordinal)
262-
.map(|value| absolute.saturating_add(*value));
263-
}
264-
ordinal = ordinal.saturating_sub(chunk.line_breaks.len());
265-
absolute = absolute.saturating_add(chunk.len());
266-
}
267-
None
263+
fn nth_line_break(&self, ordinal: usize) -> Option<usize> {
264+
let chunk_idx = self.line_break_ends.partition_point(|end| *end <= ordinal);
265+
let preceding_breaks = chunk_idx
266+
.checked_sub(1)
267+
.and_then(|idx| self.line_break_ends.get(idx))
268+
.copied()
269+
.unwrap_or(0);
270+
let chunk_start = chunk_idx
271+
.checked_sub(1)
272+
.and_then(|idx| self.chunk_ends.get(idx))
273+
.copied()
274+
.unwrap_or(0);
275+
self.chunks
276+
.get(chunk_idx)?
277+
.line_breaks
278+
.get(ordinal.saturating_sub(preceding_breaks))
279+
.map(|value| chunk_start.saturating_add(*value))
268280
}
269281

270282
fn locate(&self, pos: usize) -> (usize, usize) {
271283
let pos = pos.min(self.len);
272-
let mut absolute = 0usize;
273-
for (idx, chunk) in self.chunks.iter().enumerate() {
274-
let chunk_end = absolute.saturating_add(chunk.len());
275-
if pos < chunk_end {
276-
return (idx, pos.saturating_sub(absolute));
277-
}
278-
if pos == chunk_end {
279-
return (idx.saturating_add(1), 0);
280-
}
281-
absolute = chunk_end;
282-
}
283-
(self.chunks.len(), 0)
284+
let chunk_idx = self.chunk_ends.partition_point(|end| *end <= pos);
285+
let chunk_start = chunk_idx
286+
.checked_sub(1)
287+
.and_then(|idx| self.chunk_ends.get(idx))
288+
.copied()
289+
.unwrap_or(0);
290+
(chunk_idx, pos.saturating_sub(chunk_start))
284291
}
285292
}
286293

@@ -495,6 +502,20 @@ fn split_text_chunks(text: &str) -> Vec<TextChunk> {
495502
chunks
496503
}
497504

505+
fn chunk_indexes(chunks: &[TextChunk]) -> (Vec<usize>, Vec<usize>) {
506+
let mut byte_end = 0usize;
507+
let mut line_break_end = 0usize;
508+
let mut chunk_ends = Vec::with_capacity(chunks.len());
509+
let mut line_break_ends = Vec::with_capacity(chunks.len());
510+
for chunk in chunks {
511+
byte_end = byte_end.saturating_add(chunk.len());
512+
line_break_end = line_break_end.saturating_add(chunk.line_breaks.len());
513+
chunk_ends.push(byte_end);
514+
line_break_ends.push(line_break_end);
515+
}
516+
(chunk_ends, line_break_ends)
517+
}
518+
498519
fn line_break_positions(bytes: &[u8]) -> Vec<usize> {
499520
let mut positions = Vec::new();
500521
let mut idx = 0usize;
@@ -538,6 +559,24 @@ mod tests {
538559
);
539560
}
540561

562+
#[test]
563+
fn chunked_text_snapshot_clone_shares_text_and_lookup_indexes() {
564+
let text = ChunkedText::from_str(&"SELECT 1;\n".repeat(100_000));
565+
let mut snapshot = text.clone();
566+
567+
assert!(Arc::ptr_eq(&text.chunks, &snapshot.chunks));
568+
assert!(Arc::ptr_eq(&text.chunk_ends, &snapshot.chunk_ends));
569+
assert!(Arc::ptr_eq(
570+
&text.line_break_ends,
571+
&snapshot.line_break_ends
572+
));
573+
574+
let edit_at = snapshot.len() / 2;
575+
assert!(snapshot.replace_range(edit_at, edit_at, "-- pasted\n"));
576+
assert!(!Arc::ptr_eq(&text.chunks, &snapshot.chunks));
577+
assert_eq!(text.len() + "-- pasted\n".len(), snapshot.len());
578+
}
579+
541580
#[test]
542581
fn chunked_text_line_queries_match_flat_text() {
543582
let source = "a\r\nb\nc\rd".repeat(10_000);

0 commit comments

Comments
 (0)