Skip to content

Commit 862a1dd

Browse files
committed
자동 포멧팅 기능 개선
1 parent 8f9e5aa commit 862a1dd

8 files changed

Lines changed: 725 additions & 16 deletions

File tree

.github/workflows/release.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,31 @@ jobs:
5252
done
5353
cargo install cargo-about --locked --version 0.9.0 --features cli
5454
cargo about generate --locked about.hbs -o generated-third-party-dependencies.md
55-
cmp THIRD_PARTY_DEPENDENCIES.md generated-third-party-dependencies.md
55+
# cargo-about can order crates in Used-by lists differently by host.
56+
python3 - <<'PY'
57+
import re
58+
from pathlib import Path
59+
60+
used_by_block = re.compile(r"(?m)(?<=^Used by:\n)(?:^- .*\n)+")
61+
62+
def normalize_used_by(source: str, destination: str) -> None:
63+
text = Path(source).read_text(encoding="utf-8")
64+
normalized = used_by_block.sub(
65+
lambda match: "".join(sorted(match.group().splitlines(keepends=True))),
66+
text,
67+
)
68+
Path(destination).write_text(normalized, encoding="utf-8")
69+
70+
normalize_used_by(
71+
"THIRD_PARTY_DEPENDENCIES.md",
72+
"normalized-third-party-dependencies.md",
73+
)
74+
normalize_used_by(
75+
"generated-third-party-dependencies.md",
76+
"normalized-generated-third-party-dependencies.md",
77+
)
78+
PY
79+
cmp normalized-third-party-dependencies.md normalized-generated-third-party-dependencies.md
5680
5781
build:
5882
name: Build ${{ matrix.name }}

docs/auto_format_rule.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Never derive structural depth from:
7676

7777
## 4. Sibling frames keep the first child inline
7878

79-
For list and condition frames:
79+
The default `Stacked` layout for list and condition frames is:
8080

8181
- the first direct child remains on the owner line;
8282
- the second and later children start new lines at the frame depth;
@@ -99,6 +99,34 @@ operator advances only its innermost owning frame.
9999
A later separator must not retrospectively move or partially rewrite the first
100100
child.
101101

102+
The `Wrapped` comma-list preference changes only eligible comma separators in
103+
ordinary lists. The formatter keeps the next child inline when its canonical
104+
single-line width fits the configured right margin; otherwise that child starts
105+
a new line at the same frame depth. Structural separators, CTE siblings,
106+
multiline children, comments, and dedicated `CREATE TABLE` column/partition
107+
layouts keep their required line breaks in both modes.
108+
109+
### 4.1 Comma-list preferences
110+
111+
The Preferences dialog exposes these settings under `SQL Formatting`:
112+
113+
| Setting | Values | Default | Contract |
114+
| --- | --- | --- | --- |
115+
| `Comma Lists` | `Stacked`, `Wrapped` | `Stacked` | `Stacked` always breaks before the second and later eligible children. `Wrapped` keeps adding children while they fit the right margin. |
116+
| `Right Margin` | `60` through `300` columns | `120` | Used only by `Wrapped`. It is a comma-break target, not a hard text-splitting boundary. |
117+
118+
The right-margin calculation includes the existing line prefix, indentation,
119+
comma, following space, and the next child's inline width. A child that cannot
120+
be split safely may exceed the margin. Changing the editor window width does
121+
not change formatting output.
122+
123+
The comma-list preference applies to ordinary `SELECT`, `FROM`, `SET`,
124+
`VALUES`, `GROUP BY`, `ORDER BY`, `WINDOW`, `INTO`, `USING`, `RETURNING`, and
125+
direct parenthesized lists such as function arguments, `IN (...)`, insert
126+
columns, and row values. It does not override grammar-required line breaks,
127+
CTE separators, comments, multiline `CASE` or subqueries, trigger/grant syntax,
128+
or dedicated `CREATE TABLE` column and partition formatting.
129+
102130
## 5. Structural body boundaries are separate frames
103131

104132
The first-child-inline rule applies to sibling-bearing list and condition

docs/formatting.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ Auto-formatting rebuilds a canonical layout from tokens and structural state; it
88
does not merely adjust existing whitespace. Indentation is currently fixed at
99
four spaces.
1010

11+
The Preferences dialog exposes two comma-list layouts. `Stacked` is the default
12+
and starts the second and later children on new lines. `Wrapped` keeps eligible
13+
children on the current line until the configured right margin (default 120
14+
columns) would be exceeded. The margin is a comma-break target, not a hard text
15+
split; structural multiline syntax and dedicated `CREATE TABLE` layouts are not
16+
collapsed.
17+
1118
The normative frame ownership, child-depth, and close-boundary contract is
1219
defined in [Auto-Formatting Frame and Depth Rules](auto_format_rule.md).
1320

src/ui/main_window.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8096,6 +8096,8 @@ impl MainWindow {
80968096
config.lazy_fetch_batch_size = settings.lazy_fetch_batch_size;
80978097
config.connection_pool_size = settings.connection_pool_size;
80988098
config.cancel_timeout_seconds = settings.cancel_timeout_seconds;
8099+
config.sql_comma_list_layout = settings.sql_comma_list_layout;
8100+
config.sql_format_right_margin = settings.sql_format_right_margin;
80998101
config.save()
81008102
};
81018103
if pool_size_changed {

src/ui/settings_dialog.rs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use fltk::{
66
frame::Frame,
77
group::{Flex, FlexType, Group, Tabs},
88
input::{Input, IntInput},
9+
menu::Choice,
910
prelude::*,
1011
window::Window,
1112
};
@@ -18,8 +19,9 @@ fn fold_for_case_insensitive(value: &str) -> String {
1819
use crate::ui::constants::*;
1920
use crate::ui::{available_font_names, center_on_main, theme};
2021
use crate::utils::{
21-
AppConfig, MAX_CANCEL_TIMEOUT_SECONDS, MAX_CONNECTION_POOL_SIZE, MAX_LAZY_FETCH_BATCH_SIZE,
22-
MIN_CANCEL_TIMEOUT_SECONDS, MIN_CONNECTION_POOL_SIZE, MIN_LAZY_FETCH_BATCH_SIZE,
22+
AppConfig, SqlCommaListLayout, MAX_CANCEL_TIMEOUT_SECONDS, MAX_CONNECTION_POOL_SIZE,
23+
MAX_LAZY_FETCH_BATCH_SIZE, MAX_SQL_FORMAT_RIGHT_MARGIN, MIN_CANCEL_TIMEOUT_SECONDS,
24+
MIN_CONNECTION_POOL_SIZE, MIN_LAZY_FETCH_BATCH_SIZE, MIN_SQL_FORMAT_RIGHT_MARGIN,
2325
};
2426

2527
pub struct FontSettings {
@@ -31,6 +33,8 @@ pub struct FontSettings {
3133
pub lazy_fetch_batch_size: u32,
3234
pub connection_pool_size: u32,
3335
pub cancel_timeout_seconds: u32,
36+
pub sql_comma_list_layout: SqlCommaListLayout,
37+
pub sql_format_right_margin: u32,
3438
}
3539

3640
fn validate_size(label: &str, value: &str) -> Option<u32> {
@@ -121,6 +125,23 @@ fn validate_cancel_timeout_seconds(value: &str) -> Option<u32> {
121125
}
122126
}
123127

128+
fn validate_sql_format_right_margin(value: &str) -> Option<u32> {
129+
match value.trim().parse::<u32>() {
130+
Ok(margin)
131+
if (MIN_SQL_FORMAT_RIGHT_MARGIN..=MAX_SQL_FORMAT_RIGHT_MARGIN).contains(&margin) =>
132+
{
133+
Some(margin)
134+
}
135+
_ => {
136+
crate::ui::alert_on_main(&format!(
137+
"SQL format right margin must be a number between {} and {}.",
138+
MIN_SQL_FORMAT_RIGHT_MARGIN, MAX_SQL_FORMAT_RIGHT_MARGIN
139+
));
140+
None
141+
}
142+
}
143+
}
144+
124145
fn refill_font_list(
125146
browser: &mut HoldBrowser,
126147
all_fonts: &[String],
@@ -423,6 +444,68 @@ pub fn show_settings_dialog(config: &AppConfig) -> Option<FontSettings> {
423444
connection_group.resizable(&connection_flex);
424445
connection_group.end();
425446

447+
let mut formatting_group = Group::new(content_x, tab_body_y, content_w, tab_body_h, None);
448+
formatting_group.set_label("SQL Formatting");
449+
formatting_group.set_color(theme::panel_bg());
450+
formatting_group.set_label_color(theme::text_secondary());
451+
formatting_group.begin();
452+
453+
let mut formatting_flex = Flex::new(
454+
content_x + DIALOG_MARGIN,
455+
tab_body_y + DIALOG_MARGIN,
456+
content_w - DIALOG_MARGIN * 2,
457+
tab_body_h - DIALOG_MARGIN * 2,
458+
None,
459+
);
460+
formatting_flex.set_type(FlexType::Column);
461+
formatting_flex.set_spacing(DIALOG_SPACING);
462+
463+
let mut comma_layout_row = Flex::default().with_size(0, INPUT_ROW_HEIGHT);
464+
comma_layout_row.set_type(FlexType::Row);
465+
comma_layout_row.set_spacing(DIALOG_SPACING);
466+
let mut comma_layout_label = Frame::default().with_label("Comma Lists:");
467+
comma_layout_label.set_label_color(theme::text_primary());
468+
comma_layout_row.fixed(&comma_layout_label, FORM_LABEL_WIDTH);
469+
let mut comma_layout_choice = Choice::default();
470+
comma_layout_choice.add_choice("Stacked|Wrapped");
471+
comma_layout_choice.set_value(match config.sql_comma_list_layout {
472+
SqlCommaListLayout::Stacked => 0,
473+
SqlCommaListLayout::Wrapped => 1,
474+
});
475+
comma_layout_choice.set_color(theme::input_bg());
476+
comma_layout_choice.set_text_color(theme::text_primary());
477+
comma_layout_row.end();
478+
formatting_flex.fixed(&comma_layout_row, INPUT_ROW_HEIGHT);
479+
480+
let mut right_margin_row = Flex::default().with_size(0, INPUT_ROW_HEIGHT);
481+
right_margin_row.set_type(FlexType::Row);
482+
right_margin_row.set_spacing(DIALOG_SPACING);
483+
let mut right_margin_label = Frame::default().with_label("Right Margin:");
484+
right_margin_label.set_label_color(theme::text_primary());
485+
right_margin_row.fixed(&right_margin_label, FORM_LABEL_WIDTH);
486+
let mut right_margin_input = IntInput::default();
487+
right_margin_input.set_value(&config.normalized_sql_format_right_margin().to_string());
488+
right_margin_input.set_color(theme::input_bg());
489+
right_margin_input.set_text_color(theme::text_primary());
490+
if config.sql_comma_list_layout == SqlCommaListLayout::Stacked {
491+
right_margin_input.deactivate();
492+
}
493+
right_margin_row.end();
494+
formatting_flex.fixed(&right_margin_row, INPUT_ROW_HEIGHT);
495+
496+
let mut formatting_hint = Frame::default().with_label(&format!(
497+
"Wrapped margin: {} ~ {} columns",
498+
MIN_SQL_FORMAT_RIGHT_MARGIN, MAX_SQL_FORMAT_RIGHT_MARGIN
499+
));
500+
formatting_hint.set_label_color(theme::text_secondary());
501+
formatting_flex.fixed(&formatting_hint, LABEL_ROW_HEIGHT);
502+
503+
let formatting_filler = Frame::default();
504+
formatting_flex.resizable(&formatting_filler);
505+
formatting_flex.end();
506+
formatting_group.resizable(&formatting_flex);
507+
formatting_group.end();
508+
426509
tabs.end();
427510

428511
let mut button_row = Flex::new(
@@ -510,6 +593,15 @@ pub fn show_settings_dialog(config: &AppConfig) -> Option<FontSettings> {
510593
}
511594
});
512595

596+
let mut right_margin_input_for_layout = right_margin_input.clone();
597+
comma_layout_choice.set_callback(move |choice| {
598+
if choice.value() == 1 {
599+
right_margin_input_for_layout.activate();
600+
} else {
601+
right_margin_input_for_layout.deactivate();
602+
}
603+
});
604+
513605
let result = Arc::new(Mutex::new(None::<FontSettings>));
514606
let result_for_ok = result.clone();
515607
let mut dialog_handle = dialog.clone();
@@ -520,6 +612,8 @@ pub fn show_settings_dialog(config: &AppConfig) -> Option<FontSettings> {
520612
let lazy_fetch_batch_input_ok = lazy_fetch_batch_input.clone();
521613
let pool_size_input_ok = pool_size_input.clone();
522614
let cancel_timeout_input_ok = cancel_timeout_input.clone();
615+
let comma_layout_choice_ok = comma_layout_choice.clone();
616+
let right_margin_input_ok = right_margin_input.clone();
523617
let selected_font_ok = selected_font.clone();
524618
ok_btn.set_callback(move |_| {
525619
let ui_size = match validate_ui_size(&global_size_input_ok.value()) {
@@ -554,6 +648,16 @@ pub fn show_settings_dialog(config: &AppConfig) -> Option<FontSettings> {
554648
Some(seconds) => seconds,
555649
None => return,
556650
};
651+
let sql_comma_list_layout = if comma_layout_choice_ok.value() == 1 {
652+
SqlCommaListLayout::Wrapped
653+
} else {
654+
SqlCommaListLayout::Stacked
655+
};
656+
let sql_format_right_margin =
657+
match validate_sql_format_right_margin(&right_margin_input_ok.value()) {
658+
Some(margin) => margin,
659+
None => return,
660+
};
557661
let font = selected_font_ok
558662
.lock()
559663
.unwrap_or_else(|poisoned| poisoned.into_inner())
@@ -574,6 +678,8 @@ pub fn show_settings_dialog(config: &AppConfig) -> Option<FontSettings> {
574678
lazy_fetch_batch_size,
575679
connection_pool_size,
576680
cancel_timeout_seconds,
681+
sql_comma_list_layout,
682+
sql_format_right_margin,
577683
});
578684
dialog_handle.hide();
579685
app::awake();

src/ui/sql_editor/execution.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,6 +2017,7 @@ impl SqlEditorWidget {
20172017
let mut buffer = self.buffer.clone();
20182018
let selection = buffer.selection_position();
20192019
let preferred_db_type = Some(self.current_db_type());
2020+
let format_config = AppConfig::load();
20202021
if let Some((start, end)) = selection {
20212022
if start != end {
20222023
let buffer_len = buffer.length().max(0);
@@ -2033,8 +2034,12 @@ impl SqlEditorWidget {
20332034
start,
20342035
end,
20352036
);
2036-
let formatted =
2037-
Self::format_for_auto_formatting_with_db_type(&source, true, preferred_db_type);
2037+
let formatted = Self::format_for_auto_formatting_with_config(
2038+
&source,
2039+
true,
2040+
preferred_db_type,
2041+
&format_config,
2042+
);
20382043
if formatted == source {
20392044
return;
20402045
}
@@ -2078,8 +2083,12 @@ impl SqlEditorWidget {
20782083
}
20792084

20802085
let full_text = buffer.text();
2081-
let formatted =
2082-
Self::format_for_auto_formatting_with_db_type(&full_text, false, preferred_db_type);
2086+
let formatted = Self::format_for_auto_formatting_with_config(
2087+
&full_text,
2088+
false,
2089+
preferred_db_type,
2090+
&format_config,
2091+
);
20832092
if formatted == full_text {
20842093
return;
20852094
}

0 commit comments

Comments
 (0)