practicode 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,319 @@
1
+ use crate::core::{AppState, DIFFICULTIES, LANGUAGES, THEMES, UI_LANGUAGES};
2
+
3
+ pub(super) struct SettingsChange {
4
+ pub reload_editor: bool,
5
+ }
6
+
7
+ pub(super) fn row_count() -> usize {
8
+ 4 + LANGUAGES.len() + UI_LANGUAGES.len()
9
+ }
10
+
11
+ pub(super) fn render(state: &AppState, cursor: Option<usize>) -> String {
12
+ let settings = &state.settings;
13
+ let ui_language = settings.ui_language.as_str();
14
+ let topics = list_or_none(&settings.topics, ui_language);
15
+ let avoid = list_or_none(&settings.avoid_topics, ui_language);
16
+ let generate_languages = list_or_all(&settings.generate_languages, ui_language);
17
+ let generate_ui_languages = list_or_all(&settings.generate_ui_languages, ui_language);
18
+ let mut lines = vec![
19
+ label(ui_language, "title").to_string(),
20
+ String::new(),
21
+ label(ui_language, "instructions").to_string(),
22
+ String::new(),
23
+ row(
24
+ cursor,
25
+ 0,
26
+ &format!(
27
+ "{}: {}",
28
+ label(ui_language, "code_language"),
29
+ settings.language
30
+ ),
31
+ ),
32
+ row(
33
+ cursor,
34
+ 1,
35
+ &format!(
36
+ "{}: {}",
37
+ label(ui_language, "ui_language"),
38
+ settings.ui_language
39
+ ),
40
+ ),
41
+ row(
42
+ cursor,
43
+ 2,
44
+ &format!("{}: {}", label(ui_language, "theme"), settings.theme),
45
+ ),
46
+ row(
47
+ cursor,
48
+ 3,
49
+ &format!(
50
+ "{}: {}",
51
+ label(ui_language, "difficulty"),
52
+ settings.difficulty
53
+ ),
54
+ ),
55
+ String::new(),
56
+ format!("{}: {topics}", label(ui_language, "preferred_topics")),
57
+ format!("{}: {avoid}", label(ui_language, "avoid_topics")),
58
+ format!(
59
+ "{}: {generate_languages}",
60
+ label(ui_language, "generated_answer_languages")
61
+ ),
62
+ format!(
63
+ "{}: {generate_ui_languages}",
64
+ label(ui_language, "generated_ui_languages")
65
+ ),
66
+ format!("AI provider: {}", settings.ai_provider),
67
+ format!(
68
+ "AI model: {}",
69
+ if settings.ai_model == "auto" {
70
+ label(ui_language, "provider_default")
71
+ } else {
72
+ settings.ai_model.as_str()
73
+ }
74
+ ),
75
+ String::new(),
76
+ label(ui_language, "answer_toggles").to_string(),
77
+ ];
78
+ for (index, language) in LANGUAGES.iter().enumerate() {
79
+ let row_index = 4 + index;
80
+ let checked = generate_language_enabled(state, language);
81
+ lines.push(row(
82
+ cursor,
83
+ row_index,
84
+ &format!("{} {language}", checkbox(checked)),
85
+ ));
86
+ }
87
+ lines.push(String::new());
88
+ lines.push(label(ui_language, "ui_toggles").to_string());
89
+ for (index, language) in UI_LANGUAGES.iter().enumerate() {
90
+ let row_index = 4 + LANGUAGES.len() + index;
91
+ let checked = generate_ui_language_enabled(state, language);
92
+ lines.push(row(
93
+ cursor,
94
+ row_index,
95
+ &format!("{} {language}", checkbox(checked)),
96
+ ));
97
+ }
98
+ lines.extend([
99
+ String::new(),
100
+ label(ui_language, "commands").to_string(),
101
+ "/profile".to_string(),
102
+ "/difficulty auto|easy|medium|hard".to_string(),
103
+ "/topics arrays, strings".to_string(),
104
+ "/avoid dp, graph".to_string(),
105
+ "/generate-languages all|python, rust".to_string(),
106
+ "/generate-ui all|en, ko".to_string(),
107
+ "/provider codex|claude".to_string(),
108
+ "/model auto".to_string(),
109
+ ]);
110
+ lines.join("\n")
111
+ }
112
+
113
+ fn label<'a>(ui_language: &str, key: &'a str) -> &'a str {
114
+ if ui_language == "ko" {
115
+ match key {
116
+ "title" => "사용자 프로필",
117
+ "instructions" => "위/아래로 이동하고 Space 또는 Enter로 변경/토글",
118
+ "code_language" => "코드 언어",
119
+ "ui_language" => "UI 언어",
120
+ "theme" => "테마",
121
+ "difficulty" => "난이도",
122
+ "preferred_topics" => "선호 주제",
123
+ "avoid_topics" => "피할 주제",
124
+ "generated_answer_languages" => "생성 정답 언어",
125
+ "generated_ui_languages" => "생성 문제 언어",
126
+ "provider_default" => "auto (provider 기본값)",
127
+ "answer_toggles" => "생성 정답 언어 토글",
128
+ "ui_toggles" => "생성 문제 언어 토글",
129
+ "commands" => "명령",
130
+ "none" => "(없음)",
131
+ "all" => "전체",
132
+ _ => key,
133
+ }
134
+ } else {
135
+ match key {
136
+ "title" => "User profile",
137
+ "instructions" => "Use up/down to move. Press Space or Enter to cycle/toggle.",
138
+ "code_language" => "Code language",
139
+ "ui_language" => "UI language",
140
+ "theme" => "Theme",
141
+ "difficulty" => "Difficulty",
142
+ "preferred_topics" => "Preferred topics",
143
+ "avoid_topics" => "Avoid topics",
144
+ "generated_answer_languages" => "Generated answer languages",
145
+ "generated_ui_languages" => "Generated UI languages",
146
+ "provider_default" => "auto (provider default)",
147
+ "answer_toggles" => "Generated answer language toggles",
148
+ "ui_toggles" => "Generated problem text language toggles",
149
+ "commands" => "Commands",
150
+ "none" => "(none)",
151
+ "all" => "all",
152
+ _ => key,
153
+ }
154
+ }
155
+ }
156
+
157
+ pub(super) fn apply_selected(state: &mut AppState, selected: usize) -> SettingsChange {
158
+ let mut reload_editor = false;
159
+ match selected {
160
+ 0 => {
161
+ let current = LANGUAGES
162
+ .iter()
163
+ .position(|language| language == &state.settings.language)
164
+ .unwrap_or(0);
165
+ state.settings.language = LANGUAGES[(current + 1) % LANGUAGES.len()].to_string();
166
+ reload_editor = true;
167
+ }
168
+ 1 => {
169
+ let current = UI_LANGUAGES
170
+ .iter()
171
+ .position(|language| language == &state.settings.ui_language)
172
+ .unwrap_or(0);
173
+ state.settings.ui_language =
174
+ UI_LANGUAGES[(current + 1) % UI_LANGUAGES.len()].to_string();
175
+ }
176
+ 2 => {
177
+ let current = THEMES
178
+ .iter()
179
+ .position(|theme| theme == &state.settings.theme)
180
+ .unwrap_or(0);
181
+ state.settings.theme = THEMES[(current + 1) % THEMES.len()].to_string();
182
+ }
183
+ 3 => {
184
+ let current = DIFFICULTIES
185
+ .iter()
186
+ .position(|difficulty| difficulty == &state.settings.difficulty)
187
+ .unwrap_or(0);
188
+ let difficulty = DIFFICULTIES[(current + 1) % DIFFICULTIES.len()].to_string();
189
+ state.settings.difficulty = difficulty.clone();
190
+ if difficulty != "auto" {
191
+ state.suggested_next_difficulty = difficulty;
192
+ }
193
+ }
194
+ row if row < 4 + LANGUAGES.len() => {
195
+ toggle_generate_language(state, LANGUAGES[row - 4]);
196
+ }
197
+ row if row < row_count() => {
198
+ toggle_generate_ui_language(state, UI_LANGUAGES[row - 4 - LANGUAGES.len()]);
199
+ }
200
+ _ => {}
201
+ }
202
+ SettingsChange { reload_editor }
203
+ }
204
+
205
+ fn row(cursor: Option<usize>, index: usize, text: &str) -> String {
206
+ let marker = if cursor == Some(index) { ">" } else { " " };
207
+ format!("{marker} {text}")
208
+ }
209
+
210
+ fn generate_language_enabled(state: &AppState, language: &str) -> bool {
211
+ state.settings.generate_languages.is_empty()
212
+ || state
213
+ .settings
214
+ .generate_languages
215
+ .iter()
216
+ .any(|value| value == language)
217
+ }
218
+
219
+ fn generate_ui_language_enabled(state: &AppState, language: &str) -> bool {
220
+ state.settings.generate_ui_languages.is_empty()
221
+ || state
222
+ .settings
223
+ .generate_ui_languages
224
+ .iter()
225
+ .any(|value| value == language)
226
+ }
227
+
228
+ fn toggle_generate_language(state: &mut AppState, language: &str) {
229
+ if state.settings.generate_languages.is_empty() {
230
+ state.settings.generate_languages = LANGUAGES
231
+ .iter()
232
+ .filter(|value| **value != language)
233
+ .map(|value| (*value).to_string())
234
+ .collect();
235
+ return;
236
+ }
237
+ if generate_language_enabled(state, language) {
238
+ if state.settings.generate_languages.len() > 1 {
239
+ state
240
+ .settings
241
+ .generate_languages
242
+ .retain(|value| value != language);
243
+ }
244
+ } else {
245
+ state.settings.generate_languages.push(language.to_string());
246
+ state.settings.generate_languages = LANGUAGES
247
+ .iter()
248
+ .filter(|value| {
249
+ state
250
+ .settings
251
+ .generate_languages
252
+ .iter()
253
+ .any(|selected| selected == *value)
254
+ })
255
+ .map(|value| (*value).to_string())
256
+ .collect();
257
+ if state.settings.generate_languages.len() == LANGUAGES.len() {
258
+ state.settings.generate_languages.clear();
259
+ }
260
+ }
261
+ }
262
+
263
+ fn toggle_generate_ui_language(state: &mut AppState, language: &str) {
264
+ if state.settings.generate_ui_languages.is_empty() {
265
+ state.settings.generate_ui_languages = UI_LANGUAGES
266
+ .iter()
267
+ .filter(|value| **value != language)
268
+ .map(|value| (*value).to_string())
269
+ .collect();
270
+ return;
271
+ }
272
+ if generate_ui_language_enabled(state, language) {
273
+ if state.settings.generate_ui_languages.len() > 1 {
274
+ state
275
+ .settings
276
+ .generate_ui_languages
277
+ .retain(|value| value != language);
278
+ }
279
+ } else {
280
+ state
281
+ .settings
282
+ .generate_ui_languages
283
+ .push(language.to_string());
284
+ state.settings.generate_ui_languages = UI_LANGUAGES
285
+ .iter()
286
+ .filter(|value| {
287
+ state
288
+ .settings
289
+ .generate_ui_languages
290
+ .iter()
291
+ .any(|selected| selected == *value)
292
+ })
293
+ .map(|value| (*value).to_string())
294
+ .collect();
295
+ if state.settings.generate_ui_languages.len() == UI_LANGUAGES.len() {
296
+ state.settings.generate_ui_languages.clear();
297
+ }
298
+ }
299
+ }
300
+
301
+ fn list_or_none(values: &[String], ui_language: &str) -> String {
302
+ if values.is_empty() {
303
+ label(ui_language, "none").to_string()
304
+ } else {
305
+ values.join(", ")
306
+ }
307
+ }
308
+
309
+ fn list_or_all(values: &[String], ui_language: &str) -> String {
310
+ if values.is_empty() {
311
+ label(ui_language, "all").to_string()
312
+ } else {
313
+ values.join(", ")
314
+ }
315
+ }
316
+
317
+ fn checkbox(checked: bool) -> &'static str {
318
+ if checked { "[x]" } else { "[ ]" }
319
+ }