claude-sdk-tutor 0.1.6__tar.gz → 0.1.7__tar.gz
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.
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/PKG-INFO +1 -1
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/app.py +68 -48
- claude_sdk_tutor-0.1.7/phosphor.tcss +122 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/pyproject.toml +1 -1
- claude_sdk_tutor-0.1.7/src/claude/widgets.py +87 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/uv.lock +1 -1
- claude_sdk_tutor-0.1.6/src/claude/widgets.py +0 -29
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/.gitignore +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/.python-version +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/CLAUDE.md +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/LICENSE +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/Makefile +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/README.md +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/__init__.py +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/src/__init__.py +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/src/claude/__init__.py +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/src/claude/claude_agent.py +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/src/claude/history.py +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/src/claude/mcp_commands.py +0 -0
- {claude_sdk_tutor-0.1.6 → claude_sdk_tutor-0.1.7}/src/claude/mcp_config.py +0 -0
|
@@ -11,7 +11,8 @@ from rich.markdown import Markdown as RichMarkdown
|
|
|
11
11
|
from rich.panel import Panel
|
|
12
12
|
from textual.app import App, ComposeResult
|
|
13
13
|
from textual.containers import Vertical
|
|
14
|
-
from
|
|
14
|
+
from rich.box import ROUNDED
|
|
15
|
+
from textual.widgets import Static, Footer, Input, RichLog
|
|
15
16
|
|
|
16
17
|
from claude.claude_agent import (
|
|
17
18
|
connect_client,
|
|
@@ -21,10 +22,21 @@ from claude.claude_agent import (
|
|
|
21
22
|
from claude.history import CommandHistory
|
|
22
23
|
from claude.mcp_commands import McpAsyncCommand, McpCommandHandler
|
|
23
24
|
from claude.mcp_config import McpConfigManager
|
|
24
|
-
from claude.widgets import HistoryInput
|
|
25
|
+
from claude.widgets import ASCIISpinner, HistoryInput, StatusBar
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Message border colors (vivid)
|
|
29
|
+
USER_COLOR = "#00aaff" # Vivid cyan-blue
|
|
30
|
+
CLAUDE_COLOR = "#ff3333" # Vivid red
|
|
31
|
+
TOOL_COLOR = "#cccccc" # Bright grey
|
|
32
|
+
SYSTEM_COLOR = "#33ff66" # Vivid green
|
|
33
|
+
|
|
34
|
+
HEADER_TEXT = "Claude SDK Tutor"
|
|
25
35
|
|
|
26
36
|
|
|
27
37
|
class MyApp(App):
|
|
38
|
+
CSS_PATH = "phosphor.tcss"
|
|
39
|
+
|
|
28
40
|
def __init__(self):
|
|
29
41
|
super().__init__()
|
|
30
42
|
self.tutor_mode = True
|
|
@@ -44,66 +56,71 @@ class MyApp(App):
|
|
|
44
56
|
mcp_servers=self.mcp_config.get_enabled_servers_for_sdk(),
|
|
45
57
|
)
|
|
46
58
|
|
|
47
|
-
CSS = """
|
|
48
|
-
#main {
|
|
49
|
-
height: 100%;
|
|
50
|
-
}
|
|
51
|
-
Input {
|
|
52
|
-
height: auto;
|
|
53
|
-
margin-top: 1;
|
|
54
|
-
margin-left: 3;
|
|
55
|
-
margin-right: 3;
|
|
56
|
-
margin-bottom: 1;
|
|
57
|
-
}
|
|
58
|
-
#header {
|
|
59
|
-
content-align: center middle;
|
|
60
|
-
width: 100%;
|
|
61
|
-
margin-top: 1;
|
|
62
|
-
margin-bottom: 1;
|
|
63
|
-
height: auto;
|
|
64
|
-
}
|
|
65
|
-
RichLog {
|
|
66
|
-
background: $boost;
|
|
67
|
-
margin-left: 3;
|
|
68
|
-
margin-right: 3;
|
|
69
|
-
height: 1fr;
|
|
70
|
-
}
|
|
71
|
-
LoadingIndicator {
|
|
72
|
-
height: auto;
|
|
73
|
-
margin-left: 3;
|
|
74
|
-
margin-right: 3;
|
|
75
|
-
}
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
59
|
def compose(self) -> ComposeResult:
|
|
79
60
|
with Vertical(id="main"):
|
|
80
|
-
yield Static(
|
|
61
|
+
yield Static(HEADER_TEXT, id="header")
|
|
62
|
+
yield StatusBar(id="status-bar")
|
|
81
63
|
yield RichLog(markup=True, highlight=True)
|
|
82
|
-
yield
|
|
83
|
-
yield HistoryInput(
|
|
64
|
+
yield ASCIISpinner(id="spinner")
|
|
65
|
+
yield HistoryInput(
|
|
66
|
+
history=self.history,
|
|
67
|
+
placeholder="Type a message or /help for commands...",
|
|
68
|
+
)
|
|
84
69
|
yield Footer()
|
|
85
70
|
|
|
86
71
|
async def on_mount(self) -> None:
|
|
87
|
-
self.query_one("#spinner",
|
|
72
|
+
self.query_one("#spinner", ASCIISpinner).display = False
|
|
73
|
+
self._update_status_bar()
|
|
88
74
|
await connect_client(self.client)
|
|
89
75
|
|
|
76
|
+
def _update_status_bar(self) -> None:
|
|
77
|
+
"""Update the status bar with current mode states."""
|
|
78
|
+
status_bar = self.query_one("#status-bar", StatusBar)
|
|
79
|
+
status_bar.tutor_on = self.tutor_mode
|
|
80
|
+
status_bar.web_on = self.web_search_enabled
|
|
81
|
+
status_bar.mcp_count = len(self.mcp_config.get_enabled_servers_for_sdk())
|
|
82
|
+
|
|
90
83
|
def write_user_message(self, message: str) -> None:
|
|
91
84
|
log = self.query_one(RichLog)
|
|
92
|
-
log.write(Panel(
|
|
85
|
+
log.write(Panel(
|
|
86
|
+
RichMarkdown(message),
|
|
87
|
+
title="You",
|
|
88
|
+
border_style=USER_COLOR,
|
|
89
|
+
box=ROUNDED,
|
|
90
|
+
padding=(1, 2),
|
|
91
|
+
))
|
|
93
92
|
|
|
94
93
|
def write_system_message(self, message: str) -> None:
|
|
95
94
|
log = self.query_one(RichLog)
|
|
96
|
-
log.write(Panel(
|
|
95
|
+
log.write(Panel(
|
|
96
|
+
RichMarkdown(message),
|
|
97
|
+
title="Claude",
|
|
98
|
+
border_style=CLAUDE_COLOR,
|
|
99
|
+
box=ROUNDED,
|
|
100
|
+
padding=(1, 2),
|
|
101
|
+
))
|
|
97
102
|
|
|
98
103
|
def write_tool_message(self, name: str, input: dict) -> None:
|
|
99
104
|
log = self.query_one(RichLog)
|
|
100
105
|
input_str = json.dumps(input, indent=2)
|
|
101
106
|
content = f"**{name}**\n```json\n{input_str}\n```"
|
|
102
|
-
log.write(Panel(
|
|
107
|
+
log.write(Panel(
|
|
108
|
+
RichMarkdown(content),
|
|
109
|
+
title="Tool",
|
|
110
|
+
border_style=TOOL_COLOR,
|
|
111
|
+
box=ROUNDED,
|
|
112
|
+
padding=(1, 2),
|
|
113
|
+
))
|
|
103
114
|
|
|
104
115
|
def write_slash_message(self, message: str) -> None:
|
|
105
116
|
log = self.query_one(RichLog)
|
|
106
|
-
log.write(Panel(
|
|
117
|
+
log.write(Panel(
|
|
118
|
+
RichMarkdown(message),
|
|
119
|
+
title="System",
|
|
120
|
+
border_style=SYSTEM_COLOR,
|
|
121
|
+
box=ROUNDED,
|
|
122
|
+
padding=(1, 2),
|
|
123
|
+
))
|
|
107
124
|
|
|
108
125
|
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
109
126
|
command = event.value.strip()
|
|
@@ -136,7 +153,7 @@ class MyApp(App):
|
|
|
136
153
|
self._handle_mcp_command(command)
|
|
137
154
|
return
|
|
138
155
|
self.write_user_message(event.value)
|
|
139
|
-
self.query_one("#spinner",
|
|
156
|
+
self.query_one("#spinner", ASCIISpinner).start("Processing query...")
|
|
140
157
|
self._query_running = True
|
|
141
158
|
self.run_worker(self.get_response(event.value))
|
|
142
159
|
|
|
@@ -144,6 +161,7 @@ class MyApp(App):
|
|
|
144
161
|
self.query_one(RichLog).clear()
|
|
145
162
|
self.client = self._create_client()
|
|
146
163
|
await connect_client(self.client)
|
|
164
|
+
self._update_status_bar()
|
|
147
165
|
self.write_slash_message("Context cleared")
|
|
148
166
|
|
|
149
167
|
async def toggle_tutor_mode(self) -> None:
|
|
@@ -151,7 +169,8 @@ class MyApp(App):
|
|
|
151
169
|
self.query_one(RichLog).clear()
|
|
152
170
|
self.client = self._create_client()
|
|
153
171
|
await connect_client(self.client)
|
|
154
|
-
|
|
172
|
+
self._update_status_bar()
|
|
173
|
+
status = "enabled" if self.tutor_mode else "disabled"
|
|
155
174
|
self.write_slash_message(f"Tutor mode {status}")
|
|
156
175
|
|
|
157
176
|
async def toggle_web_search(self) -> None:
|
|
@@ -159,7 +178,8 @@ class MyApp(App):
|
|
|
159
178
|
self.query_one(RichLog).clear()
|
|
160
179
|
self.client = self._create_client()
|
|
161
180
|
await connect_client(self.client)
|
|
162
|
-
|
|
181
|
+
self._update_status_bar()
|
|
182
|
+
status = "enabled" if self.web_search_enabled else "disabled"
|
|
163
183
|
self.write_slash_message(f"Web search {status}")
|
|
164
184
|
|
|
165
185
|
def show_help(self) -> None:
|
|
@@ -183,7 +203,7 @@ class MyApp(App):
|
|
|
183
203
|
)
|
|
184
204
|
elif isinstance(result, McpAsyncCommand):
|
|
185
205
|
# Async command needs connection testing
|
|
186
|
-
self.query_one("#spinner",
|
|
206
|
+
self.query_one("#spinner", ASCIISpinner).start("Testing MCP connections...")
|
|
187
207
|
self.run_worker(self._test_mcp_connections(result))
|
|
188
208
|
else:
|
|
189
209
|
self.write_slash_message(result)
|
|
@@ -236,7 +256,7 @@ class MyApp(App):
|
|
|
236
256
|
except Exception as e:
|
|
237
257
|
self.write_slash_message(f"**Error** testing MCP connections: {e}")
|
|
238
258
|
finally:
|
|
239
|
-
self.query_one("#spinner",
|
|
259
|
+
self.query_one("#spinner", ASCIISpinner).stop()
|
|
240
260
|
|
|
241
261
|
def _handle_mcp_add_step(self, user_input: str) -> None:
|
|
242
262
|
"""Handle a step in the interactive MCP add wizard."""
|
|
@@ -330,7 +350,7 @@ class MyApp(App):
|
|
|
330
350
|
elif isinstance(message, ResultMessage):
|
|
331
351
|
pass # Might want to add logging later
|
|
332
352
|
finally:
|
|
333
|
-
self.query_one("#spinner",
|
|
353
|
+
self.query_one("#spinner", ASCIISpinner).stop()
|
|
334
354
|
self._query_running = False
|
|
335
355
|
|
|
336
356
|
def action_cancel_query(self) -> None:
|
|
@@ -347,7 +367,7 @@ class MyApp(App):
|
|
|
347
367
|
pass # Ignore errors if not connected or no active query
|
|
348
368
|
finally:
|
|
349
369
|
self._query_running = False
|
|
350
|
-
self.query_one("#spinner",
|
|
370
|
+
self.query_one("#spinner", ASCIISpinner).stop()
|
|
351
371
|
|
|
352
372
|
|
|
353
373
|
def main():
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* MINIMAL Theme - Clean, Modern, Professional */
|
|
2
|
+
|
|
3
|
+
/* Color Variables */
|
|
4
|
+
$bg-primary: #1a1a1a;
|
|
5
|
+
$bg-secondary: #242424;
|
|
6
|
+
$bg-elevated: #2a2a2a;
|
|
7
|
+
$text-primary: #e0e0e0;
|
|
8
|
+
$text-muted: #888888;
|
|
9
|
+
$accent: #5c9fd4;
|
|
10
|
+
$accent-dim: #4a7fa8;
|
|
11
|
+
$border: #333333;
|
|
12
|
+
$border-focus: #555555;
|
|
13
|
+
$success: #6fbf73;
|
|
14
|
+
$warning: #d4a05c;
|
|
15
|
+
|
|
16
|
+
/* Global App Styling */
|
|
17
|
+
Screen {
|
|
18
|
+
background: $bg-primary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Main Container */
|
|
22
|
+
#main {
|
|
23
|
+
height: 100%;
|
|
24
|
+
background: $bg-primary;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Header */
|
|
28
|
+
#header {
|
|
29
|
+
content-align: center middle;
|
|
30
|
+
width: 100%;
|
|
31
|
+
padding: 1 0;
|
|
32
|
+
height: auto;
|
|
33
|
+
color: $text-primary;
|
|
34
|
+
text-style: bold;
|
|
35
|
+
background: $bg-primary;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Status Bar */
|
|
39
|
+
#status-bar {
|
|
40
|
+
content-align: center middle;
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: auto;
|
|
43
|
+
padding-bottom: 1;
|
|
44
|
+
color: $text-muted;
|
|
45
|
+
background: $bg-primary;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Message Log */
|
|
49
|
+
RichLog {
|
|
50
|
+
background: $bg-secondary;
|
|
51
|
+
margin-left: 2;
|
|
52
|
+
margin-right: 2;
|
|
53
|
+
height: 1fr;
|
|
54
|
+
border: round $border;
|
|
55
|
+
scrollbar-color: $border;
|
|
56
|
+
scrollbar-color-hover: $border-focus;
|
|
57
|
+
scrollbar-color-active: $accent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Spinner */
|
|
61
|
+
#spinner {
|
|
62
|
+
height: auto;
|
|
63
|
+
margin-left: 2;
|
|
64
|
+
margin-right: 2;
|
|
65
|
+
color: $text-muted;
|
|
66
|
+
background: $bg-primary;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Input Field */
|
|
70
|
+
HistoryInput {
|
|
71
|
+
height: auto;
|
|
72
|
+
margin-top: 1;
|
|
73
|
+
margin-left: 2;
|
|
74
|
+
margin-right: 2;
|
|
75
|
+
margin-bottom: 1;
|
|
76
|
+
background: $bg-elevated;
|
|
77
|
+
color: $text-primary;
|
|
78
|
+
border: round $border;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
HistoryInput:focus {
|
|
82
|
+
border: round $accent-dim;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
HistoryInput > .input--cursor {
|
|
86
|
+
color: $bg-primary;
|
|
87
|
+
background: $accent;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
HistoryInput > .input--placeholder {
|
|
91
|
+
color: $text-muted;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Footer */
|
|
95
|
+
Footer {
|
|
96
|
+
background: $bg-secondary;
|
|
97
|
+
color: $text-muted;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
Footer > .footer--key {
|
|
101
|
+
background: $bg-elevated;
|
|
102
|
+
color: $text-primary;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Footer > .footer--description {
|
|
106
|
+
color: $text-muted;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
FooterKey {
|
|
110
|
+
background: $bg-elevated;
|
|
111
|
+
color: $text-primary;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
FooterKey:hover {
|
|
115
|
+
background: $accent-dim;
|
|
116
|
+
color: $bg-primary;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Hide the default loading indicator */
|
|
120
|
+
LoadingIndicator {
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from textual.binding import Binding
|
|
2
|
+
from textual.reactive import reactive
|
|
3
|
+
from textual.widgets import Input, Static
|
|
4
|
+
|
|
5
|
+
from claude.history import CommandHistory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StatusBar(Static):
|
|
9
|
+
"""Reactive status bar showing tutor/web/mcp states."""
|
|
10
|
+
|
|
11
|
+
tutor_on: reactive[bool] = reactive(True)
|
|
12
|
+
web_on: reactive[bool] = reactive(False)
|
|
13
|
+
mcp_count: reactive[int] = reactive(0)
|
|
14
|
+
|
|
15
|
+
def render(self) -> str:
|
|
16
|
+
tutor = "on" if self.tutor_on else "off"
|
|
17
|
+
web = "on" if self.web_on else "off"
|
|
18
|
+
mcp = f"{self.mcp_count} server{'s' if self.mcp_count != 1 else ''}"
|
|
19
|
+
return f"tutor: {tutor} · web: {web} · mcp: {mcp}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ASCIISpinner(Static):
|
|
23
|
+
"""Minimal spinner that cycles through frames with a label."""
|
|
24
|
+
|
|
25
|
+
SPINNER_FRAMES = ["· ", "·· ", "···", " ··", " ·", " "]
|
|
26
|
+
|
|
27
|
+
_frame: reactive[int] = reactive(0)
|
|
28
|
+
_label: reactive[str] = reactive("")
|
|
29
|
+
_running: reactive[bool] = reactive(False)
|
|
30
|
+
|
|
31
|
+
def __init__(self, label: str = "Processing...", **kwargs):
|
|
32
|
+
super().__init__(**kwargs)
|
|
33
|
+
self._label = label
|
|
34
|
+
self._timer = None
|
|
35
|
+
|
|
36
|
+
def render(self) -> str:
|
|
37
|
+
if not self._running:
|
|
38
|
+
return ""
|
|
39
|
+
frame = self.SPINNER_FRAMES[self._frame % len(self.SPINNER_FRAMES)]
|
|
40
|
+
return f"{frame} {self._label}"
|
|
41
|
+
|
|
42
|
+
def start(self, label: str = "Processing query...") -> None:
|
|
43
|
+
"""Start the spinner animation."""
|
|
44
|
+
self._label = label
|
|
45
|
+
self._running = True
|
|
46
|
+
self._frame = 0
|
|
47
|
+
self.display = True
|
|
48
|
+
if self._timer is None:
|
|
49
|
+
self._timer = self.set_interval(0.1, self._advance_frame)
|
|
50
|
+
|
|
51
|
+
def stop(self) -> None:
|
|
52
|
+
"""Stop the spinner animation."""
|
|
53
|
+
self._running = False
|
|
54
|
+
self.display = False
|
|
55
|
+
if self._timer is not None:
|
|
56
|
+
self._timer.stop()
|
|
57
|
+
self._timer = None
|
|
58
|
+
|
|
59
|
+
def _advance_frame(self) -> None:
|
|
60
|
+
"""Advance to the next spinner frame."""
|
|
61
|
+
if self._running:
|
|
62
|
+
self._frame = (self._frame + 1) % len(self.SPINNER_FRAMES)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class HistoryInput(Input):
|
|
66
|
+
"""Input widget with command history navigation."""
|
|
67
|
+
|
|
68
|
+
BINDINGS = [
|
|
69
|
+
Binding("up", "history_previous", "Previous command", show=False),
|
|
70
|
+
Binding("down", "history_next", "Next command", show=False),
|
|
71
|
+
Binding("escape", "app.cancel_query", "Cancel", show=False),
|
|
72
|
+
Binding("ctrl+c", "app.cancel_query", "Cancel", show=False),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
def __init__(self, history: CommandHistory, **kwargs):
|
|
76
|
+
super().__init__(**kwargs)
|
|
77
|
+
self.history = history
|
|
78
|
+
|
|
79
|
+
def action_history_previous(self) -> None:
|
|
80
|
+
"""Navigate to previous command in history."""
|
|
81
|
+
self.value = self.history.navigate_up(self.value)
|
|
82
|
+
self.cursor_position = len(self.value)
|
|
83
|
+
|
|
84
|
+
def action_history_next(self) -> None:
|
|
85
|
+
"""Navigate to next command in history."""
|
|
86
|
+
self.value = self.history.navigate_down(self.value)
|
|
87
|
+
self.cursor_position = len(self.value)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from textual.binding import Binding
|
|
2
|
-
from textual.widgets import Input
|
|
3
|
-
|
|
4
|
-
from claude.history import CommandHistory
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class HistoryInput(Input):
|
|
8
|
-
"""Input widget with command history navigation."""
|
|
9
|
-
|
|
10
|
-
BINDINGS = [
|
|
11
|
-
Binding("up", "history_previous", "Previous command", show=False),
|
|
12
|
-
Binding("down", "history_next", "Next command", show=False),
|
|
13
|
-
Binding("escape", "app.cancel_query", "Cancel", show=False),
|
|
14
|
-
Binding("ctrl+c", "app.cancel_query", "Cancel", show=False),
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
def __init__(self, history: CommandHistory, **kwargs):
|
|
18
|
-
super().__init__(**kwargs)
|
|
19
|
-
self.history = history
|
|
20
|
-
|
|
21
|
-
def action_history_previous(self) -> None:
|
|
22
|
-
"""Navigate to previous command in history."""
|
|
23
|
-
self.value = self.history.navigate_up(self.value)
|
|
24
|
-
self.cursor_position = len(self.value)
|
|
25
|
-
|
|
26
|
-
def action_history_next(self) -> None:
|
|
27
|
-
"""Navigate to next command in history."""
|
|
28
|
-
self.value = self.history.navigate_down(self.value)
|
|
29
|
-
self.cursor_position = len(self.value)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|