Skip to content

Commit 95e8b8e

Browse files
updated code to use newer widgets
1 parent 82094de commit 95e8b8e

File tree

5 files changed

+81
-119
lines changed

5 files changed

+81
-119
lines changed

CLI-Exercises/README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ You'll need to install `textual` first. See [Textual documentation](https://text
1313
$ python3 -m venv textual_apps
1414
$ cd textual_apps
1515
$ source bin/activate
16-
$ pip install textual==0.5.0
16+
$ pip install textual==0.14.0
1717

1818
$ git clone --depth 1 https://github.com/learnbyexample/TUI-apps.git
1919
$ cd TUI-apps/CLI-Exercises
@@ -27,31 +27,33 @@ After pressing **Ctrl+n** twice, you should get a screen similar to the one show
2727
# Guide
2828

2929
* Press **Ctrl+p** and **Ctrl+n** to navigate the questions list.
30-
* **Ctrl+←** and **Ctrl+→** can be used instead.
3130
* You can also click them using mouse from the footer.
3231
* Type the command in the box below the question. Cursor focus is meant to be always within this box.
3332
* Press **Enter** to execute the command.
3433
* `/bin/sh` is the shell used. So, features like `echo {1..3}` (brace expansion) won't work.
3534
* Output would be displayed below the command box.
3635
* If the output matches the expected results, the command box will turn *green* and a reference solution will also be shown.
3736
* Issues due to errors and timeout (about `2` seconds) will be displayed in *red*.
38-
* Contents of the input file and expected output are shown at the bottom of the screen. You might have to scroll down to view longer files.
37+
* Contents of the input file and expected output are shown at the bottom of the screen.
38+
* You might have to scroll (using mouse or the scrollbar) for longer files.
3939
* Press **Ctrl+s** to show the reference solution if you are unable to solve an exercise.
4040
* Press **Ctrl+t** to toggle between light and dark themes.
4141
* Press **Ctrl+q** or **Ctrl+c** to quit the app.
4242
* Navigating and editing in the command box:
4343
* Use mouse click to position the cursor anywhere you like
4444
* **** move left by one character
4545
* **** move right by one character
46-
* **Ctrl+a** move to the start of the line
47-
* **Ctrl+e** move to the end of the line
48-
* **Ctrl+w** delete backwards till whitespace boundary or start of the line
49-
* **Ctrl+f** delete forwards till whitespace boundary or end of the line
50-
* **Ctrl+u** delete backwards till start of the line
51-
* **Ctrl+k** delete forwards till end of the line
52-
* **Backspace** delete character to the left of the cursor
53-
* **Ctrl+d** delete character to the right of the cursor
54-
* Use **Page Up** and **Page Down** (or mouse) to scroll.
46+
* **Home** or **Ctrl+a** move to the start of the line
47+
* **End** or **Ctrl+e** move to the end of the line
48+
* **Ctrl+←** move to the start of the current/previous word
49+
* **Ctrl+→** move to the start of the next word
50+
* **Ctrl+w** delete till the start of the current/previous word
51+
* **Ctrl+f** delete till the start of the next word
52+
* **Ctrl+u** delete till the start of the line
53+
* **Ctrl+k** delete till the end of the line
54+
* **Backspace** or **Ctrl+h** delete character to the left of the cursor
55+
* **Delete** or **Ctrl+d** delete character under the cursor
56+
* **Enter** submit the code for execution
5557

5658
> **Note**
5759
> Commands you have typed are automatically saved in `user_progress.json` (only when you press **Enter** to execute a command — navigating to another question and closing the app won't trigger the save logic). Theme choice is also saved. If you close the application and open it again, the first unsolved question will be displayed (i.e. already solved questions are skipped). If you use **Ctrl+s**, the solution *won't* be saved in `user_progress.json` — you'll have to navigate to another question and back (or close and open the app) to be considered for saving the changes. Once you have solved a question, only a different correct solution can override the previously saved command.

CLI-Exercises/cli_exercises.css

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
#header {
22
dock: top;
33
text-align: center;
4+
color: black;
45
background: orange;
6+
width: 100%;
57
}
68

79
#question {
810
border: blank;
911
padding: 0 1 0 1;
12+
width: 100%;
1013
}
1114

1215
#user_cmd_output {
1316
min-height: 3;
1417
margin-bottom: 1;
18+
width: 100%;
1519
}
1620

17-
#ip_op {
21+
.container {
1822
height: auto;
23+
align: center middle;
1924
}
2025

21-
#sample_input {
22-
dock: left;
26+
.ip_op {
2327
width: 50%;
2428
}
25-
26-
#expected_output {
27-
dock: right;
28-
width: 49%;
29-
}

CLI-Exercises/cli_exercises.png

279 Bytes
Loading

CLI-Exercises/cli_exercises.py

Lines changed: 20 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,23 @@
11
from textual.app import App
22
from textual.binding import Binding
3-
from textual.containers import Container
3+
from textual.containers import Horizontal, Vertical
44
from textual.widgets import Footer, Label, Input
55
from rich.panel import Panel
66
from rich.markup import escape
7+
from rich.markdown import Markdown
78

89
import json
9-
import os
1010
import subprocess
11-
import re
1211
from functools import partial
1312

1413
class CLIExercisesApp(App):
1514
CSS_PATH = 'cli_exercises.css'
1615
BINDINGS = [
1716
Binding('ctrl+s', 'show_answer', 'Solution', show=True),
18-
Binding('ctrl+p,ctrl+left', 'previous', 'Prev', show=True),
19-
Binding('ctrl+n,ctrl+right', 'next', 'Next', show=True),
17+
Binding('ctrl+p', 'previous', 'Prev', show=True),
18+
Binding('ctrl+n', 'next', 'Next', show=True),
2019
('ctrl+t', 'toggle_theme', 'Theme'),
2120
('ctrl+q', 'app.quit', 'Quit'),
22-
Binding('ctrl+u', 'clear_till_start',
23-
'Clear till start of line', show=False),
24-
Binding('ctrl+k', 'clear_till_end',
25-
'Clear till end of line', show=False),
26-
Binding('ctrl+w', 'clear_till_prev_space',
27-
'Clear till previous space or start of line', show=False),
28-
Binding('ctrl+f', 'clear_till_next_space',
29-
'Clear till next space or end of line', show=False),
30-
Binding('ctrl+a', 'move_to_start',
31-
'Move cursor to start of line', show=False),
32-
Binding('ctrl+e', 'move_to_end',
33-
'Move cursor to end of line', show=False),
3421
]
3522

3623
def __init__(self):
@@ -42,31 +29,35 @@ def __init__(self):
4229
self.q_max_idx = len(self.questions) - 1
4330

4431
self.question = Label(id='question')
45-
self.sample_input = Label(id='sample_input')
46-
self.expected_output = Label(id='expected_output')
32+
self.sample_input = Label(classes='ip_op')
33+
self.expected_output = Label(classes='ip_op')
4734

4835
placeholder = 'Type your command here. Press Enter to execute the command.'
49-
self.user_cmd_input = Input(placeholder=placeholder, id='user_cmd_input')
36+
self.user_cmd_input = Input(placeholder=placeholder)
5037
self.user_cmd_output = Label(id='user_cmd_output')
5138
self.op_panel = partial(Panel, title='Output', title_align='left')
5239

5340
self.progress_file = 'user_progress.json'
54-
if os.path.isfile(self.progress_file):
41+
try:
5542
with open(self.progress_file) as f:
5643
self.user_progress = {int(k): v for k,v in json.load(f).items()}
44+
except FileNotFoundError:
45+
self.user_progress = {}
46+
else:
5747
for idx in range(self.q_max_idx + 1):
5848
if not self.user_progress.get(idx, ('', False))[1]:
5949
break
6050
self.q_idx = idx
61-
else:
62-
self.user_progress = {}
6351

6452
def compose(self):
6553
yield Label('[b]Linux CLI Text Processing Exercises', id='header')
66-
yield self.question
67-
yield self.user_cmd_input
68-
yield self.user_cmd_output
69-
yield Container(self.sample_input, self.expected_output, id='ip_op')
54+
with Vertical(classes='container'):
55+
yield self.question
56+
yield self.user_cmd_input
57+
yield self.user_cmd_output
58+
with Horizontal(classes='container'):
59+
yield self.sample_input
60+
yield self.expected_output
7061
yield Footer()
7162

7263
def on_mount(self):
@@ -119,8 +110,8 @@ def process_user_cmd(self):
119110
def set_quest_ip_op(self):
120111
self.answered_correctly = False
121112
self.show_answer_clicked = False
122-
quest = f"Q{self.q_idx + 1}) {self.questions[self.q_idx]['question']}"
123-
self.question.update(quest)
113+
self.question.update(Markdown(f'Q{self.q_idx+1}) ' +
114+
self.questions[self.q_idx]['question']))
124115

125116
ip_file = self.questions[self.q_idx]['ip_file']
126117
with open(ip_file) as f:
@@ -184,36 +175,6 @@ def action_toggle_theme(self):
184175
self.user_progress[-1] = self.dark
185176
self.write_progress_file()
186177

187-
def action_clear_till_start(self):
188-
cmd = self.user_cmd_input.value[self.user_cmd_input.cursor_position:]
189-
self.user_cmd_input.value = cmd
190-
self.user_cmd_input.cursor_position = 0
191-
192-
def action_clear_till_end(self):
193-
cmd = self.user_cmd_input.value[:self.user_cmd_input.cursor_position]
194-
self.user_cmd_input.value = cmd
195-
self.user_cmd_input.cursor_position = len(cmd)
196-
197-
def action_clear_till_prev_space(self):
198-
cmd = self.user_cmd_input.value
199-
pos = self.user_cmd_input.cursor_position
200-
s = re.sub(r'\S*\s*$', '', cmd[:pos])
201-
self.user_cmd_input.value = s + cmd[pos:]
202-
self.user_cmd_input.cursor_position = len(s)
203-
204-
def action_clear_till_next_space(self):
205-
cmd = self.user_cmd_input.value
206-
pos = self.user_cmd_input.cursor_position
207-
s = re.sub(r'^\s*\S*', '', cmd[pos:])
208-
self.user_cmd_input.value = cmd[:pos] + s
209-
self.user_cmd_input.cursor_position = pos
210-
211-
def action_move_to_start(self):
212-
self.user_cmd_input.cursor_position = 0
213-
214-
def action_move_to_end(self):
215-
self.user_cmd_input.cursor_position = len(self.user_cmd_input.value)
216-
217178

218179
if __name__ == '__main__':
219180
app = CLIExercisesApp()

0 commit comments

Comments
 (0)