practicode
Advanced tools
@@ -32,2 +32,7 @@ { | ||
| "hint_result": "drag select to copy | F6 view | /next step | /home", | ||
| "learning_primary_next": "Enter: Next", | ||
| "learning_primary_run": "F5: Run", | ||
| "learning_primary_edit": "Enter: Edit", | ||
| "learning_primary_home": "Enter: Home", | ||
| "learning_primary_leave_code": "Esc: Leave editor", | ||
| "hint_idle": "/ command | ? help", | ||
@@ -34,0 +39,0 @@ "hint_busy": "Working | q quit", |
@@ -32,2 +32,7 @@ { | ||
| "hint_result": "arrastra para copiar | F6 vista | /next paso | /home", | ||
| "learning_primary_next": "Enter: Siguiente", | ||
| "learning_primary_run": "F5: Ejecutar", | ||
| "learning_primary_edit": "Enter: Volver al código", | ||
| "learning_primary_home": "Enter: Inicio", | ||
| "learning_primary_leave_code": "Esc: Salir del editor", | ||
| "hint_idle": "/ comando | ? ayuda", | ||
@@ -34,0 +39,0 @@ "hint_busy": "Trabajando | q salir", |
@@ -32,2 +32,7 @@ { | ||
| "hint_result": "ドラッグ選択でコピー | F6 表示 | /next ステップ | /home", | ||
| "learning_primary_next": "Enter: 次へ", | ||
| "learning_primary_run": "F5: 実行", | ||
| "learning_primary_edit": "Enter: コードへ", | ||
| "learning_primary_home": "Enter: ホームへ", | ||
| "learning_primary_leave_code": "Esc: 編集を終了", | ||
| "hint_idle": "/ コマンド | ? ヘルプ", | ||
@@ -34,0 +39,0 @@ "hint_busy": "処理中 | q 終了", |
@@ -32,2 +32,7 @@ { | ||
| "hint_result": "드래그 선택으로 복사 | F6 보기 | /next 단계 | /home", | ||
| "learning_primary_next": "Enter: 다음", | ||
| "learning_primary_run": "F5: 실행", | ||
| "learning_primary_edit": "Enter: 코드로", | ||
| "learning_primary_home": "Enter: 홈으로", | ||
| "learning_primary_leave_code": "Esc: 편집 끝내기", | ||
| "hint_idle": "/ 명령 | ? 도움말", | ||
@@ -34,0 +39,0 @@ "hint_busy": "작업 중 | q 종료", |
@@ -32,2 +32,7 @@ { | ||
| "hint_result": "拖拽选择复制 | F6 视图 | /next 步骤 | /home", | ||
| "learning_primary_next": "Enter:下一步", | ||
| "learning_primary_run": "F5:运行", | ||
| "learning_primary_edit": "Enter:返回代码", | ||
| "learning_primary_home": "Enter:返回首页", | ||
| "learning_primary_leave_code": "Esc:结束编辑", | ||
| "hint_idle": "/ 命令 | ? 帮助", | ||
@@ -34,0 +39,0 @@ "hint_busy": "处理中 | q 退出", |
+1
-1
@@ -456,3 +456,3 @@ # This file is automatically @generated by Cargo. | ||
| name = "practicode" | ||
| version = "0.2.2" | ||
| version = "0.2.3" | ||
| dependencies = [ | ||
@@ -459,0 +459,0 @@ "anyhow", |
+1
-1
| [package] | ||
| name = "practicode" | ||
| version = "0.2.2" | ||
| version = "0.2.3" | ||
| edition = "2024" | ||
@@ -5,0 +5,0 @@ description = "Offline-first programming language lessons and coding practice in a Rust terminal UI." |
+1
-1
| { | ||
| "name": "practicode", | ||
| "version": "0.2.2", | ||
| "version": "0.2.3", | ||
| "description": "Offline-first programming language lessons and coding practice in a Rust terminal UI.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
+11
-0
@@ -306,2 +306,13 @@ use super::*; | ||
| } | ||
| if self.mode == AppMode::Learn && !self.show_output && key.code == KeyCode::Enter { | ||
| match self.learning_session.step() { | ||
| LearningStep::Complete => self.action_home()?, | ||
| LearningStep::Exercise if self.learning_session.view() == LearningView::Result => { | ||
| self.action_edit()? | ||
| } | ||
| LearningStep::Exercise => {} | ||
| _ => self.action_next_learning()?, | ||
| } | ||
| return Ok(()); | ||
| } | ||
| self.handle_global_shortcut(key) | ||
@@ -308,0 +319,0 @@ } |
+17
-1
@@ -283,4 +283,20 @@ use super::*; | ||
| }; | ||
| let context = if step == LearningStep::Exercise { | ||
| String::new() | ||
| } else { | ||
| format!( | ||
| "\n\n## {}\n\n{}", | ||
| learning_step_label(language, LearningStep::Exercise), | ||
| learning_exercise(lesson, language) | ||
| ) | ||
| }; | ||
| let position = match step { | ||
| LearningStep::Review | LearningStep::Delta => 1, | ||
| LearningStep::Predict => 2, | ||
| LearningStep::Exercise => 3, | ||
| LearningStep::Reflect => 4, | ||
| LearningStep::Complete => unreachable!(), | ||
| }; | ||
| format!( | ||
| "# {}: {title}\n\n{body}", | ||
| "# {position}/4 · {}: {title}\n\n{body}{context}", | ||
| learning_step_label(language, step) | ||
@@ -287,0 +303,0 @@ ) |
+37
-13
@@ -16,2 +16,5 @@ use super::*; | ||
| } | ||
| if self.mode == AppMode::Learn { | ||
| return format!(" {} ", self.learning_primary_hint()); | ||
| } | ||
| let lang = &self.state.settings.ui_language; | ||
@@ -36,2 +39,12 @@ let key = match self.mode { | ||
| if self.mode == AppMode::Learn { | ||
| let primary = if self.task_rx.is_some() | ||
| || self.editing_notes | ||
| || self.focus == Focus::Command | ||
| || self.list_cursor.is_some() | ||
| || self.show_output | ||
| { | ||
| self.mode_hint() | ||
| } else { | ||
| self.learning_primary_hint() | ||
| }; | ||
| let lesson = current_syntax_lesson(&self.state, &self.state.settings.language); | ||
@@ -45,14 +58,14 @@ let (done, total) = | ||
| }; | ||
| let context = if !self.show_output && self.focus == selected_focus { | ||
| let focus = if !self.show_output && self.focus == selected_focus { | ||
| format!( | ||
| "{}: {} | {}", | ||
| " | {}: {}", | ||
| ui_text(lang, "focus_active"), | ||
| learning_view_label(lang, self.learning_session.view()), | ||
| self.mode_hint() | ||
| ) | ||
| } else { | ||
| self.mode_hint().to_string() | ||
| String::new() | ||
| }; | ||
| return format!( | ||
| " PRACTICODE | {} | {} | {} | {done}/{total} | {context} | {}:{} ", | ||
| " {} | PRACTICODE | {} | {} | {} | {done}/{total}{focus} | {}:{} ", | ||
| primary, | ||
| ui_text(lang, "mode_learn"), | ||
@@ -227,11 +240,4 @@ syntax_language_name(&self.state.settings.language), | ||
| } | ||
| if self.mode == AppMode::Learn | ||
| && !self.show_output | ||
| && self.learning_session.view() == LearningView::Result | ||
| && self.focus != Focus::Command | ||
| { | ||
| return ui_text(lang, "hint_result"); | ||
| } | ||
| if self.mode == AppMode::Learn && !self.show_output && self.focus != Focus::Command { | ||
| return ui_text(lang, "hint_learn"); | ||
| return self.learning_primary_hint(); | ||
| } | ||
@@ -251,2 +257,20 @@ if self.mode == AppMode::Problems && !self.show_output && self.focus != Focus::Command { | ||
| fn learning_primary_hint(&self) -> &'static str { | ||
| let key = if self.focus == Focus::Code | ||
| && self.learning_session.step() != LearningStep::Exercise | ||
| { | ||
| "learning_primary_leave_code" | ||
| } else { | ||
| match self.learning_session.step() { | ||
| LearningStep::Exercise if self.learning_session.view() == LearningView::Result => { | ||
| "learning_primary_edit" | ||
| } | ||
| LearningStep::Exercise => "learning_primary_run", | ||
| LearningStep::Complete => "learning_primary_home", | ||
| _ => "learning_primary_next", | ||
| } | ||
| }; | ||
| ui_text(&self.state.settings.ui_language, key) | ||
| } | ||
| pub(super) fn help_text(&self) -> String { | ||
@@ -253,0 +277,0 @@ let lang = &self.state.settings.ui_language; |
+10
-14
@@ -849,3 +849,3 @@ use super::*; | ||
| assert!(!app.wants_mouse_capture()); | ||
| assert!(app.status_text().contains("drag select to copy")); | ||
| assert!(app.status_text().contains("Enter: Next")); | ||
| app.focus_command(); | ||
@@ -1053,5 +1053,5 @@ assert!(app.wants_mouse_capture()); | ||
| #[test] | ||
| fn compact_learning_status_keeps_every_primary_action_visible() { | ||
| fn compact_learning_status_leads_with_the_current_primary_action() { | ||
| for language in UI_LANGUAGES { | ||
| for width in [60, 80, 99, 100] { | ||
| for width in [60, 80, 99, 100, 121, 140] { | ||
| let mut app = | ||
@@ -1062,14 +1062,10 @@ PracticodeApp::new(tmp_root(&format!("compact-status-{language}-{width}"))) | ||
| app.action_learn("python").unwrap(); | ||
| let terminal = draw_at(&mut app, width, 24); | ||
| let row = terminal.backend().buffer().area.height - 2; | ||
| let rendered = (0..width) | ||
| .map(|x| terminal.backend().buffer()[(x, row)].symbol()) | ||
| .collect::<String>(); | ||
| let _terminal = draw_at(&mut app, width, 24); | ||
| let rendered = app.status_text_for_width(width); | ||
| for action in ["/next", "F5", "F6", "F1"] { | ||
| assert!( | ||
| rendered.contains(action), | ||
| "{language} {width}: missing {action} in {rendered:?}" | ||
| ); | ||
| } | ||
| let action = ui_text(language, "learning_primary_next"); | ||
| assert!( | ||
| rendered.trim_start().starts_with(action), | ||
| "{language} {width}: {action} does not lead {rendered:?}" | ||
| ); | ||
| } | ||
@@ -1076,0 +1072,0 @@ } |
1768701
0.17%16262
0.15%