Skip to content

Commit 27decd7

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

5 files changed

Lines changed: 176 additions & 94 deletions

File tree

src/ui/main_window.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,7 +1832,27 @@ impl AppState {
18321832
self.set_tab_dirty(tab_id, is_dirty);
18331833
}
18341834

1835-
fn on_tab_buffer_modified(&mut self, tab_id: QueryTabId, ins: i32, del: i32, buf: &TextBuffer) {
1835+
fn dirty_state_from_equal_length_local_edit(
1836+
pristine_text: &str,
1837+
was_dirty: bool,
1838+
start: usize,
1839+
inserted_text: &str,
1840+
) -> Option<bool> {
1841+
let end = start.saturating_add(inserted_text.len());
1842+
if pristine_text.get(start..end) != Some(inserted_text) {
1843+
return Some(true);
1844+
}
1845+
(!was_dirty).then_some(false)
1846+
}
1847+
1848+
fn on_tab_buffer_modified(
1849+
&mut self,
1850+
tab_id: QueryTabId,
1851+
pos: i32,
1852+
ins: i32,
1853+
del: i32,
1854+
buf: &TextBuffer,
1855+
) {
18361856
let Some(index) = self.find_tab_index(tab_id) else {
18371857
return;
18381858
};
@@ -1850,6 +1870,19 @@ impl AppState {
18501870
return;
18511871
}
18521872

1873+
let start = pos.max(0) as usize;
1874+
let inserted_end = pos.saturating_add(ins.max(0)).min(buf.length());
1875+
let inserted_text = buf.text_range(pos.max(0), inserted_end).unwrap_or_default();
1876+
if let Some(is_dirty) = Self::dirty_state_from_equal_length_local_edit(
1877+
&tab.pristine_text,
1878+
tab.is_dirty,
1879+
start,
1880+
&inserted_text,
1881+
) {
1882+
self.set_tab_dirty(tab_id, is_dirty);
1883+
return;
1884+
}
1885+
18531886
let current_text = buf.text();
18541887
self.refresh_tab_dirty_from_text(tab_id, &current_text);
18551888
}
@@ -7273,12 +7306,12 @@ impl MainWindow {
72737306

72747307
let weak_state_for_dirty = Arc::downgrade(state);
72757308
let mut buffer_for_dirty = editor.get_buffer();
7276-
buffer_for_dirty.add_modify_callback2(move |buf, _pos, ins, del, _restyled, _deleted| {
7309+
buffer_for_dirty.add_modify_callback2(move |buf, pos, ins, del, _restyled, _deleted| {
72777310
let Some(state_for_dirty) = weak_state_for_dirty.upgrade() else {
72787311
return;
72797312
};
72807313
if let Ok(mut s) = state_for_dirty.try_lock() {
7281-
s.on_tab_buffer_modified(tab_id, ins, del, buf)
7314+
s.on_tab_buffer_modified(tab_id, pos, ins, del, buf)
72827315
};
72837316
});
72847317
}
@@ -9493,6 +9526,48 @@ mod tests {
94939526
use fltk::enums::{Key, Shortcut};
94949527
use std::sync::{Arc, Mutex};
94959528

9529+
#[test]
9530+
fn equal_length_paste_avoids_full_dirty_scan_when_local_bytes_decide_state() {
9531+
let pristine = "SELECT employee_name FROM employees;";
9532+
let start = pristine.find("employee_name").expect("column name");
9533+
9534+
assert_eq!(
9535+
AppState::dirty_state_from_equal_length_local_edit(
9536+
pristine,
9537+
false,
9538+
start,
9539+
"employee_name",
9540+
),
9541+
Some(false)
9542+
);
9543+
assert_eq!(
9544+
AppState::dirty_state_from_equal_length_local_edit(
9545+
pristine,
9546+
false,
9547+
start,
9548+
"department_id",
9549+
),
9550+
Some(true)
9551+
);
9552+
assert_eq!(
9553+
AppState::dirty_state_from_equal_length_local_edit(
9554+
pristine,
9555+
true,
9556+
start,
9557+
"department_id",
9558+
),
9559+
Some(true)
9560+
);
9561+
}
9562+
9563+
#[test]
9564+
fn equal_length_edit_scans_fully_only_when_it_may_restore_pristine_text() {
9565+
assert_eq!(
9566+
AppState::dirty_state_from_equal_length_local_edit("SELECT 1;", true, 7, "1"),
9567+
None
9568+
);
9569+
}
9570+
94969571
#[test]
94979572
fn resolve_window_shortcut_prefers_current_key_match() {
94989573
let action = MainWindow::resolve_window_shortcut_action(

src/ui/sql_editor/highlighting.rs

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use crate::ui::syntax_highlight::HighlightWordAudit;
88

99
const DEFERRED_REHIGHLIGHT_IDLE_DELAY_SECONDS: f64 = 0.15;
1010
const SEMANTIC_REHIGHLIGHT_OVERSCAN_LINES: usize = 100;
11+
const LARGE_DOCUMENT_PASTE_LINE_THRESHOLD: usize = 100_000;
12+
const LARGE_PASTE_DEFER_SEMANTIC_BYTES: usize = 64 * 1024;
1113
// Keep semantic alias work independent of the total document size. The window
1214
// is renewed only if lexical-state propagation carries highlighting beyond it.
1315
const LOCAL_ALIAS_CONTEXT_LOOKAROUND_BYTES: usize = 16 * 1024;
@@ -99,6 +101,14 @@ struct BoundedAliasContext {
99101
end: usize,
100102
}
101103

104+
fn should_defer_semantic_alias_context_for_insert(
105+
line_count: usize,
106+
inserted_text: &str,
107+
) -> bool {
108+
inserted_text.len() >= LARGE_PASTE_DEFER_SEMANTIC_BYTES
109+
|| (line_count >= LARGE_DOCUMENT_PASTE_LINE_THRESHOLD && inserted_text.len() > 1)
110+
}
111+
102112
impl BoundedAliasContext {
103113
fn covers(&self, start: usize, end: usize) -> bool {
104114
start >= self.start && end <= self.end
@@ -769,25 +779,6 @@ impl SqlEditorWidget {
769779
self.rehighlight_full_buffer();
770780
}
771781

772-
fn handle_buffer_highlight_update(
773-
&self,
774-
buf: &TextBuffer,
775-
pos: i32,
776-
ins: i32,
777-
del: i32,
778-
deleted_text: &str,
779-
) {
780-
let inserted_text = inserted_text(buf, &self.highlight_shadow, pos, ins);
781-
self.handle_buffer_highlight_update_with_known_inserted_text(
782-
buf,
783-
pos,
784-
ins,
785-
del,
786-
&inserted_text,
787-
deleted_text,
788-
);
789-
}
790-
791782
fn handle_buffer_highlight_update_with_known_inserted_text(
792783
&self,
793784
buf: &TextBuffer,
@@ -828,7 +819,7 @@ impl SqlEditorWidget {
828819
return;
829820
}
830821

831-
let updated = {
822+
let (updated, defer_semantic_alias_context) = {
832823
let mut shadow = self
833824
.highlight_shadow
834825
.lock()
@@ -847,18 +838,27 @@ impl SqlEditorWidget {
847838
return;
848839
}
849840

850-
self.apply_main_thread_incremental_highlighting(
851-
&mut shadow,
852-
&mut style_buffer,
853-
shadow_pos,
854-
inserted_text.len(),
855-
del.max(0) as usize,
856-
inserted_text,
857-
deleted_text,
841+
let defer_semantic_alias_context =
842+
should_defer_semantic_alias_context_for_insert(shadow.line_count(), inserted_text);
843+
(
844+
self.apply_main_thread_incremental_highlighting(
845+
&mut shadow,
846+
&mut style_buffer,
847+
shadow_pos,
848+
inserted_text.len(),
849+
del.max(0) as usize,
850+
inserted_text,
851+
deleted_text,
852+
defer_semantic_alias_context,
853+
),
854+
defer_semantic_alias_context,
858855
)
859856
};
860857
match updated {
861858
Some(true) | Some(false) => {
859+
if defer_semantic_alias_context {
860+
self.schedule_deferred_visible_semantic_rehighlight();
861+
}
862862
self.redraw_editor_if_live();
863863
}
864864
None => self.rehighlight_full_buffer(),
@@ -892,6 +892,7 @@ impl SqlEditorWidget {
892892
del: usize,
893893
inserted_text: &str,
894894
deleted_text: &str,
895+
defer_semantic_alias_context: bool,
895896
) -> Option<bool> {
896897
let text_len = shadow.len();
897898
if text_len == 0 {
@@ -909,8 +910,15 @@ impl SqlEditorWidget {
909910
let mut current_line_idx = shadow.line_index_for_position(start);
910911
let mut entry_state = shadow.entry_state_for_line(current_line_idx);
911912
let mut changed_range: Option<(usize, usize)> = None;
912-
let mut alias_context =
913-
shadow.bounded_alias_context(start, must_cover_end, mysql_compatible);
913+
let mut alias_context = if defer_semantic_alias_context {
914+
BoundedAliasContext {
915+
context: Arc::new(super::query_text::LocalAliasContext::default()),
916+
start: 0,
917+
end: text_len,
918+
}
919+
} else {
920+
shadow.bounded_alias_context(start, must_cover_end, mysql_compatible)
921+
};
914922

915923
while current_line_idx < shadow.line_count() {
916924
let current_start = shadow.line_start_for_index(current_line_idx);
@@ -1163,6 +1171,23 @@ mod tests {
11631171
use super::*;
11641172
use crate::ui::syntax_highlight::SqlHighlighter;
11651173

1174+
#[test]
1175+
fn large_document_paste_defers_only_semantic_alias_work() {
1176+
assert!(should_defer_semantic_alias_context_for_insert(
1177+
LARGE_DOCUMENT_PASTE_LINE_THRESHOLD,
1178+
"ab"
1179+
));
1180+
assert!(!should_defer_semantic_alias_context_for_insert(
1181+
LARGE_DOCUMENT_PASTE_LINE_THRESHOLD,
1182+
"a"
1183+
));
1184+
assert!(should_defer_semantic_alias_context_for_insert(
1185+
10,
1186+
&"x".repeat(LARGE_PASTE_DEFER_SEMANTIC_BYTES)
1187+
));
1188+
assert!(!should_defer_semantic_alias_context_for_insert(10, "SELECT"));
1189+
}
1190+
11661191
#[test]
11671192
fn incremental_direct_rehighlight_end_returns_zero_for_empty_text() {
11681193
let shadow = HighlightShadowState::default();

src/ui/sql_editor/intellisense_host.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,20 +251,60 @@ impl SqlEditorWidget {
251251
let mut buffer = self.buffer.clone();
252252
let widget = self.clone();
253253
let intellisense_runtime = self.intellisense_runtime.clone();
254+
let undo_state = self.undo_redo_state.clone();
255+
let applying_history_navigation = self.applying_history_navigation.clone();
254256
let suppress_buffer_callbacks = self.suppress_buffer_callbacks.clone();
255257
buffer.add_modify_callback2(move |buf, pos, ins, del, _restyled, deleted_text| {
256258
if ins <= 0 && del <= 0 {
257259
return;
258260
}
261+
crate::ui::sql_editor::ime_trace(|| {
262+
format!(
263+
"modify pos={pos} ins={ins} del={del} deleted_text={deleted_text:?} \
264+
compose_state={} selection={:?}",
265+
fltk::app::compose_state(),
266+
buf.selection_position(),
267+
)
268+
});
259269
if load_mutex_bool(&suppress_buffer_callbacks) {
260270
return;
261271
}
272+
let edit = BufferEdit {
273+
start: pos.max(0) as usize,
274+
deleted_len: del.max(0) as usize,
275+
inserted_text: inserted_text(buf, pos, ins),
276+
// record_edit reads the authoritative deleted bytes from its
277+
// persistent pre-edit snapshot.
278+
deleted_text: String::new(),
279+
};
280+
281+
let is_applying_navigation = *applying_history_navigation
282+
.lock()
283+
.unwrap_or_else(|poisoned| poisoned.into_inner());
284+
if !is_applying_navigation {
285+
let mut state = undo_state
286+
.lock()
287+
.unwrap_or_else(|poisoned| poisoned.into_inner());
288+
if !state.applying_history {
289+
let edit_group =
290+
classify_edit_group(ins, del, &edit.inserted_text, deleted_text);
291+
state.record_edit(&edit, edit_group);
292+
}
293+
}
294+
262295
intellisense_runtime.apply_buffer_edit(
263296
pos.max(0) as usize,
264297
ins.max(0) as usize,
265298
del.max(0) as usize,
266299
);
267-
widget.handle_buffer_highlight_update(buf, pos, ins, del, deleted_text);
300+
widget.handle_buffer_highlight_update_with_known_inserted_text(
301+
buf,
302+
pos,
303+
ins,
304+
del,
305+
&edit.inserted_text,
306+
deleted_text,
307+
);
268308
});
269309
}
270310

src/ui/sql_editor/mod.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2491,7 +2491,6 @@ impl SqlEditorWidget {
24912491
};
24922492

24932493
widget.setup_intellisense();
2494-
widget.setup_word_undo_redo();
24952494
widget.setup_syntax_highlighting();
24962495
widget.sync_db_type_from_connection();
24972496
widget.setup_progress_handler(progress_receiver, progress_callback, query_running);
@@ -5050,7 +5049,7 @@ mod execution_state_tests {
50505049
classify_edit_group, inserted_text, load_mutex_bool, load_mutex_bool_option,
50515050
try_mark_query_running, BufferEdit, EditGranularity, EditOperation, HighlightShadowState,
50525051
IntellisenseRuntimeState, QueryProgress, SqlEditorWidget, UndoDelta, UndoSnapshot,
5053-
WordUndoRedoState, MAX_WORD_UNDO_HISTORY, STYLE_DEFAULT,
5052+
WordUndoRedoState, MAX_WORD_UNDO_HISTORY,
50545053
};
50555054
use fltk::enums::Event;
50565055
use fltk::text::TextBuffer;
@@ -5422,17 +5421,10 @@ mod execution_state_tests {
54225421
let mut buffer = TextBuffer::default();
54235422
buffer.set_text(original);
54245423

5425-
let styles = std::iter::repeat_n(STYLE_DEFAULT, original.len()).collect::<String>();
5426-
let shadow = Arc::new(Mutex::new(HighlightShadowState::default()));
5427-
shadow
5428-
.lock()
5429-
.unwrap_or_else(|poisoned| poisoned.into_inner())
5430-
.rebuild(original.to_string(), &styles, Vec::new());
5431-
54325424
let pos = original.find("a FROM").unwrap_or(0);
54335425
buffer.replace(pos as i32, pos.saturating_add(1) as i32, "'");
54345426

5435-
assert_eq!(inserted_text(&buffer, &shadow, pos as i32, 1), "'");
5427+
assert_eq!(inserted_text(&buffer, pos as i32, 1), "'");
54365428
}
54375429

54385430
#[test]

0 commit comments

Comments
 (0)