notispf 1.0.2__tar.gz → 1.0.4__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.
- {notispf-1.0.2 → notispf-1.0.4}/PKG-INFO +44 -10
- {notispf-1.0.2 → notispf-1.0.4}/README.md +43 -9
- notispf-1.0.4/notispf/__init__.py +1 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/app.py +88 -10
- {notispf-1.0.2 → notispf-1.0.4}/notispf/buffer.py +15 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/commands/block_cmds.py +6 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/commands/line_cmds.py +67 -1
- {notispf-1.0.2 → notispf-1.0.4}/notispf/display.py +20 -9
- {notispf-1.0.2 → notispf-1.0.4}/notispf/find_change.py +10 -1
- {notispf-1.0.2 → notispf-1.0.4}/notispf.egg-info/PKG-INFO +44 -10
- {notispf-1.0.2 → notispf-1.0.4}/pyproject.toml +1 -1
- notispf-1.0.2/notispf/__init__.py +0 -1
- {notispf-1.0.2 → notispf-1.0.4}/notispf/__main__.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/commands/__init__.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/commands/exclude_cmds.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/commands/registry.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf/prefix.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf.egg-info/SOURCES.txt +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf.egg-info/dependency_links.txt +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf.egg-info/entry_points.txt +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/notispf.egg-info/top_level.txt +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/setup.cfg +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/tests/test_buffer.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/tests/test_exclude.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/tests/test_find_change.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/tests/test_line_cmds.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/tests/test_overlay.py +0 -0
- {notispf-1.0.2 → notispf-1.0.4}/tests/test_prefix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: notispf
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: A terminal text editor inspired by the ISPF editor from z/OS
|
|
5
5
|
Author-email: mrthock <m64357@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,7 +77,7 @@ notispf <file>
|
|
|
77
77
|
## Screen Layout
|
|
78
78
|
|
|
79
79
|
```
|
|
80
|
-
notispf filename.txt
|
|
80
|
+
notispf filename.txt Line 1/42 Col 1
|
|
81
81
|
000001|This is the first line of your file
|
|
82
82
|
000002|Second line here
|
|
83
83
|
000003|Third line
|
|
@@ -86,7 +86,7 @@ notispf <file>
|
|
|
86
86
|
Type prefix command, Enter to execute, Esc to cancel
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
- **Status bar** (top) — filename, modified flag `[+]`, current line/total
|
|
89
|
+
- **Status bar** (top) — filename, modified flag `[+]`, current line/total, cursor column, `[HEX]` when hex mode is active
|
|
90
90
|
- **Prefix column** (left, 6 chars) — shows line numbers; type commands here
|
|
91
91
|
- **Text area** (right of `|`) — edit your file
|
|
92
92
|
- **Message/command line** (bottom) — status messages and command input
|
|
@@ -132,6 +132,13 @@ You can stage commands on multiple lines before pressing Enter — they all exec
|
|
|
132
132
|
| `B` | Paste clipboard **before** this line |
|
|
133
133
|
| `>n` | Indent right n columns (e.g. `>4` adds 4 spaces) |
|
|
134
134
|
| `<n` | Indent left n columns (removes up to n leading spaces) |
|
|
135
|
+
| `HEX` | Replace this line with its hex representation |
|
|
136
|
+
| `HEXB` | Insert a hex copy of this line below it |
|
|
137
|
+
| `HEXA` | Convert a hex line back to ASCII text |
|
|
138
|
+
| `UC` | Uppercase this line |
|
|
139
|
+
| `UCn` | Uppercase n lines (e.g. `UC3`) |
|
|
140
|
+
| `LC` | Lowercase this line |
|
|
141
|
+
| `LCn` | Lowercase n lines |
|
|
135
142
|
|
|
136
143
|
### Block Commands
|
|
137
144
|
|
|
@@ -164,7 +171,7 @@ Use `A` or `B` on a third line to place the clipboard after copying or moving.
|
|
|
164
171
|
|
|
165
172
|
## Command Line
|
|
166
173
|
|
|
167
|
-
Press
|
|
174
|
+
Press **F6** to open the command line, then type a command and press Enter.
|
|
168
175
|
|
|
169
176
|
### File Commands
|
|
170
177
|
|
|
@@ -172,17 +179,37 @@ Press **`=`** or **`F6`** to open the command line, then type a command and pres
|
|
|
172
179
|
|---------|--------|
|
|
173
180
|
| `SAVE` | Save file |
|
|
174
181
|
| `FILE` | Save and exit |
|
|
175
|
-
| `CANCEL` or `QUIT` | Exit without saving |
|
|
182
|
+
| `CANCEL` or `QUIT` or `CAN` | Exit without saving |
|
|
183
|
+
|
|
184
|
+
### Undo / Redo
|
|
185
|
+
|
|
186
|
+
| Command | Action |
|
|
187
|
+
|---------|--------|
|
|
188
|
+
| `UNDO` | Undo last change |
|
|
189
|
+
| `REDO` | Redo last undone change |
|
|
190
|
+
|
|
191
|
+
### Hex Mode
|
|
192
|
+
|
|
193
|
+
| Command | Action |
|
|
194
|
+
|---------|--------|
|
|
195
|
+
| `HEX ON` | Convert entire file to hex (e.g. `Hello` → `48 65 6C 6C 6F`) |
|
|
196
|
+
| `HEX OFF` | Convert hex back to text |
|
|
197
|
+
|
|
198
|
+
`[HEX]` appears in the status bar while hex mode is active. `HEX ON` and `HEX OFF` each count as a single undo step.
|
|
176
199
|
|
|
177
200
|
### Find and Change
|
|
178
201
|
|
|
179
202
|
```
|
|
180
|
-
FIND
|
|
181
|
-
CHANGE
|
|
182
|
-
CHANGE
|
|
183
|
-
CHANGE
|
|
203
|
+
FIND 'text'
|
|
204
|
+
CHANGE 'old' 'new'
|
|
205
|
+
CHANGE 'old' 'new' ALL
|
|
206
|
+
CHANGE 'old' 'new' ALL .labelA .labelB
|
|
184
207
|
```
|
|
185
208
|
|
|
209
|
+
Both single and double quotes are accepted as delimiters.
|
|
210
|
+
|
|
211
|
+
Aliases: `F` for `FIND`, `C` for `CHANGE`.
|
|
212
|
+
|
|
186
213
|
- `FIND` — locate next occurrence (case-insensitive by default)
|
|
187
214
|
- `CHANGE` — replace next occurrence
|
|
188
215
|
- `CHANGE ... ALL` — replace all occurrences in file
|
|
@@ -197,10 +224,17 @@ Labels are used to define ranges for `CHANGE ... ALL .A .B`.
|
|
|
197
224
|
|
|
198
225
|
| Key | Action |
|
|
199
226
|
|-----|--------|
|
|
227
|
+
| F1 | Help |
|
|
200
228
|
| F3 | Save and exit |
|
|
201
|
-
| F5 |
|
|
229
|
+
| F5 | Repeat last FIND (RFIND) |
|
|
202
230
|
| F6 | Open command line |
|
|
231
|
+
| F7 / Page Up | Scroll up |
|
|
232
|
+
| F8 / Page Down | Scroll down |
|
|
233
|
+
| F10 | Scroll left |
|
|
234
|
+
| F11 | Scroll right |
|
|
203
235
|
| F12 | Exit without saving |
|
|
236
|
+
| Ctrl+Z | Undo last change |
|
|
237
|
+
| Ctrl+Y | Redo last undone change |
|
|
204
238
|
|
|
205
239
|
## Contributing
|
|
206
240
|
|
|
@@ -55,7 +55,7 @@ notispf <file>
|
|
|
55
55
|
## Screen Layout
|
|
56
56
|
|
|
57
57
|
```
|
|
58
|
-
notispf filename.txt
|
|
58
|
+
notispf filename.txt Line 1/42 Col 1
|
|
59
59
|
000001|This is the first line of your file
|
|
60
60
|
000002|Second line here
|
|
61
61
|
000003|Third line
|
|
@@ -64,7 +64,7 @@ notispf <file>
|
|
|
64
64
|
Type prefix command, Enter to execute, Esc to cancel
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
- **Status bar** (top) — filename, modified flag `[+]`, current line/total
|
|
67
|
+
- **Status bar** (top) — filename, modified flag `[+]`, current line/total, cursor column, `[HEX]` when hex mode is active
|
|
68
68
|
- **Prefix column** (left, 6 chars) — shows line numbers; type commands here
|
|
69
69
|
- **Text area** (right of `|`) — edit your file
|
|
70
70
|
- **Message/command line** (bottom) — status messages and command input
|
|
@@ -110,6 +110,13 @@ You can stage commands on multiple lines before pressing Enter — they all exec
|
|
|
110
110
|
| `B` | Paste clipboard **before** this line |
|
|
111
111
|
| `>n` | Indent right n columns (e.g. `>4` adds 4 spaces) |
|
|
112
112
|
| `<n` | Indent left n columns (removes up to n leading spaces) |
|
|
113
|
+
| `HEX` | Replace this line with its hex representation |
|
|
114
|
+
| `HEXB` | Insert a hex copy of this line below it |
|
|
115
|
+
| `HEXA` | Convert a hex line back to ASCII text |
|
|
116
|
+
| `UC` | Uppercase this line |
|
|
117
|
+
| `UCn` | Uppercase n lines (e.g. `UC3`) |
|
|
118
|
+
| `LC` | Lowercase this line |
|
|
119
|
+
| `LCn` | Lowercase n lines |
|
|
113
120
|
|
|
114
121
|
### Block Commands
|
|
115
122
|
|
|
@@ -142,7 +149,7 @@ Use `A` or `B` on a third line to place the clipboard after copying or moving.
|
|
|
142
149
|
|
|
143
150
|
## Command Line
|
|
144
151
|
|
|
145
|
-
Press
|
|
152
|
+
Press **F6** to open the command line, then type a command and press Enter.
|
|
146
153
|
|
|
147
154
|
### File Commands
|
|
148
155
|
|
|
@@ -150,17 +157,37 @@ Press **`=`** or **`F6`** to open the command line, then type a command and pres
|
|
|
150
157
|
|---------|--------|
|
|
151
158
|
| `SAVE` | Save file |
|
|
152
159
|
| `FILE` | Save and exit |
|
|
153
|
-
| `CANCEL` or `QUIT` | Exit without saving |
|
|
160
|
+
| `CANCEL` or `QUIT` or `CAN` | Exit without saving |
|
|
161
|
+
|
|
162
|
+
### Undo / Redo
|
|
163
|
+
|
|
164
|
+
| Command | Action |
|
|
165
|
+
|---------|--------|
|
|
166
|
+
| `UNDO` | Undo last change |
|
|
167
|
+
| `REDO` | Redo last undone change |
|
|
168
|
+
|
|
169
|
+
### Hex Mode
|
|
170
|
+
|
|
171
|
+
| Command | Action |
|
|
172
|
+
|---------|--------|
|
|
173
|
+
| `HEX ON` | Convert entire file to hex (e.g. `Hello` → `48 65 6C 6C 6F`) |
|
|
174
|
+
| `HEX OFF` | Convert hex back to text |
|
|
175
|
+
|
|
176
|
+
`[HEX]` appears in the status bar while hex mode is active. `HEX ON` and `HEX OFF` each count as a single undo step.
|
|
154
177
|
|
|
155
178
|
### Find and Change
|
|
156
179
|
|
|
157
180
|
```
|
|
158
|
-
FIND
|
|
159
|
-
CHANGE
|
|
160
|
-
CHANGE
|
|
161
|
-
CHANGE
|
|
181
|
+
FIND 'text'
|
|
182
|
+
CHANGE 'old' 'new'
|
|
183
|
+
CHANGE 'old' 'new' ALL
|
|
184
|
+
CHANGE 'old' 'new' ALL .labelA .labelB
|
|
162
185
|
```
|
|
163
186
|
|
|
187
|
+
Both single and double quotes are accepted as delimiters.
|
|
188
|
+
|
|
189
|
+
Aliases: `F` for `FIND`, `C` for `CHANGE`.
|
|
190
|
+
|
|
164
191
|
- `FIND` — locate next occurrence (case-insensitive by default)
|
|
165
192
|
- `CHANGE` — replace next occurrence
|
|
166
193
|
- `CHANGE ... ALL` — replace all occurrences in file
|
|
@@ -175,10 +202,17 @@ Labels are used to define ranges for `CHANGE ... ALL .A .B`.
|
|
|
175
202
|
|
|
176
203
|
| Key | Action |
|
|
177
204
|
|-----|--------|
|
|
205
|
+
| F1 | Help |
|
|
178
206
|
| F3 | Save and exit |
|
|
179
|
-
| F5 |
|
|
207
|
+
| F5 | Repeat last FIND (RFIND) |
|
|
180
208
|
| F6 | Open command line |
|
|
209
|
+
| F7 / Page Up | Scroll up |
|
|
210
|
+
| F8 / Page Down | Scroll down |
|
|
211
|
+
| F10 | Scroll left |
|
|
212
|
+
| F11 | Scroll right |
|
|
181
213
|
| F12 | Exit without saving |
|
|
214
|
+
| Ctrl+Z | Undo last change |
|
|
215
|
+
| Ctrl+Y | Redo last undone change |
|
|
182
216
|
|
|
183
217
|
## Contributing
|
|
184
218
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.4"
|
|
@@ -6,6 +6,8 @@ import os
|
|
|
6
6
|
from notispf.buffer import Buffer
|
|
7
7
|
from notispf.commands.registry import CommandRegistry
|
|
8
8
|
from notispf.commands import line_cmds, block_cmds, exclude_cmds
|
|
9
|
+
from notispf.commands.line_cmds import line_to_hex, hex_to_line
|
|
10
|
+
from notispf.buffer import Line
|
|
9
11
|
from notispf.display import Display, ViewState, TEXT_OFFSET
|
|
10
12
|
from notispf.find_change import FindChangeEngine
|
|
11
13
|
from notispf.prefix import PrefixArea
|
|
@@ -79,14 +81,23 @@ class App:
|
|
|
79
81
|
vs = self.vs
|
|
80
82
|
|
|
81
83
|
if vs.command_mode:
|
|
84
|
+
self.buffer.end_edit_group()
|
|
82
85
|
return self._handle_command_key(key)
|
|
83
86
|
|
|
84
87
|
if vs.help_mode:
|
|
88
|
+
self.buffer.end_edit_group()
|
|
85
89
|
return self._handle_help_key(key)
|
|
86
90
|
|
|
87
91
|
if vs.prefix_mode:
|
|
92
|
+
self.buffer.end_edit_group()
|
|
88
93
|
return self._handle_prefix_key(key)
|
|
89
94
|
|
|
95
|
+
# End any active edit group for non-text-edit keys
|
|
96
|
+
_text_edit_keys = {curses.KEY_BACKSPACE, 127, curses.KEY_DC,
|
|
97
|
+
curses.KEY_ENTER, ord('\n'), ord('\r')}
|
|
98
|
+
if key not in _text_edit_keys and not (32 <= key <= 126):
|
|
99
|
+
self.buffer.end_edit_group()
|
|
100
|
+
|
|
90
101
|
rows, _ = self.display.stdscr.getmaxyx()
|
|
91
102
|
content_rows = rows - 2 - (1 if vs.show_cols else 0)
|
|
92
103
|
|
|
@@ -121,11 +132,17 @@ class App:
|
|
|
121
132
|
self._scroll_col_to_cursor()
|
|
122
133
|
|
|
123
134
|
# Enter command mode
|
|
124
|
-
elif key ==
|
|
135
|
+
elif key == curses.KEY_F6:
|
|
125
136
|
vs.command_mode = True
|
|
126
137
|
vs.command_input = ""
|
|
127
138
|
vs.message = ""
|
|
128
139
|
|
|
140
|
+
# F1 = HELP
|
|
141
|
+
elif key == curses.KEY_F1:
|
|
142
|
+
vs.help_mode = True
|
|
143
|
+
vs.help_scroll = 0
|
|
144
|
+
vs.message = ""
|
|
145
|
+
|
|
129
146
|
# F3 = FILE (save and quit)
|
|
130
147
|
elif key == curses.KEY_F3:
|
|
131
148
|
self._save_and_quit()
|
|
@@ -135,13 +152,22 @@ class App:
|
|
|
135
152
|
elif key == curses.KEY_F12:
|
|
136
153
|
return True
|
|
137
154
|
|
|
138
|
-
# F5 =
|
|
155
|
+
# F5 = RFIND (repeat last find)
|
|
139
156
|
elif key == curses.KEY_F5:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
vs.message =
|
|
143
|
-
|
|
144
|
-
|
|
157
|
+
pattern = self.find_engine._last_find
|
|
158
|
+
if not pattern:
|
|
159
|
+
vs.message = "No previous FIND"
|
|
160
|
+
else:
|
|
161
|
+
pos = self.find_engine.find_next(pattern)
|
|
162
|
+
if pos:
|
|
163
|
+
vs.cursor_line, vs.cursor_col = pos
|
|
164
|
+
self._scroll_to_cursor()
|
|
165
|
+
self._scroll_col_to_cursor()
|
|
166
|
+
vs.highlight_pattern = pattern
|
|
167
|
+
vs.message = f"Found: {pattern!r}"
|
|
168
|
+
else:
|
|
169
|
+
vs.highlight_pattern = ""
|
|
170
|
+
vs.message = f"Not found: {pattern!r}"
|
|
145
171
|
|
|
146
172
|
# Tab: prefix(N) -> text(N), text(N) -> prefix(N+1)
|
|
147
173
|
elif key == ord('\t'):
|
|
@@ -173,6 +199,12 @@ class App:
|
|
|
173
199
|
vs.prefix_input = self.prefix_area._pending.get(vs.cursor_line, "")
|
|
174
200
|
vs.message = "Type prefix command, Enter to execute, Esc to cancel"
|
|
175
201
|
|
|
202
|
+
# Undo / Redo
|
|
203
|
+
elif key == ord('\x1a'): # Ctrl+Z
|
|
204
|
+
vs.message = "Undone" if self.buffer.undo() else "Nothing to undo"
|
|
205
|
+
elif key == ord('\x19'): # Ctrl+Y
|
|
206
|
+
vs.message = "Redone" if self.buffer.redo() else "Nothing to redo"
|
|
207
|
+
|
|
176
208
|
# Text editing (Phase 6 — placeholder)
|
|
177
209
|
elif key == curses.KEY_BACKSPACE or key == 127:
|
|
178
210
|
self._backspace()
|
|
@@ -231,6 +263,48 @@ class App:
|
|
|
231
263
|
|
|
232
264
|
cmd = tokens[0] if tokens else ""
|
|
233
265
|
|
|
266
|
+
_aliases = {"F": "FIND", "C": "CHANGE", "CAN": "CANCEL"}
|
|
267
|
+
cmd = _aliases.get(cmd, cmd)
|
|
268
|
+
|
|
269
|
+
if cmd == "UNDO":
|
|
270
|
+
return "Undone" if self.buffer.undo() else "Nothing to undo"
|
|
271
|
+
|
|
272
|
+
if cmd == "REDO":
|
|
273
|
+
return "Redone" if self.buffer.redo() else "Nothing to redo"
|
|
274
|
+
|
|
275
|
+
if cmd == "HEX":
|
|
276
|
+
sub = tokens[1] if len(tokens) > 1 else ""
|
|
277
|
+
if sub == "ON":
|
|
278
|
+
if self.vs.hex_mode:
|
|
279
|
+
return "Already in HEX mode"
|
|
280
|
+
self.buffer._snapshot()
|
|
281
|
+
for i, line in enumerate(self.buffer.lines):
|
|
282
|
+
self.buffer.lines[i] = Line(
|
|
283
|
+
text=line_to_hex(line.text), label=line.label, modified=True)
|
|
284
|
+
self.buffer.modified = True
|
|
285
|
+
self.vs.hex_mode = True
|
|
286
|
+
return "HEX ON — use HEX OFF to restore"
|
|
287
|
+
elif sub == "OFF":
|
|
288
|
+
if not self.vs.hex_mode:
|
|
289
|
+
return "Not in HEX mode"
|
|
290
|
+
new_lines = []
|
|
291
|
+
bad = []
|
|
292
|
+
for i, line in enumerate(self.buffer.lines):
|
|
293
|
+
try:
|
|
294
|
+
new_lines.append(Line(
|
|
295
|
+
text=hex_to_line(line.text), label=line.label, modified=True))
|
|
296
|
+
except ValueError:
|
|
297
|
+
bad.append(i + 1)
|
|
298
|
+
if bad:
|
|
299
|
+
return f"Invalid hex on line(s): {', '.join(str(n) for n in bad[:5])}"
|
|
300
|
+
self.buffer._snapshot()
|
|
301
|
+
self.buffer.lines = new_lines
|
|
302
|
+
self.buffer.modified = True
|
|
303
|
+
self.vs.hex_mode = False
|
|
304
|
+
return "HEX OFF"
|
|
305
|
+
else:
|
|
306
|
+
return "Usage: HEX ON | HEX OFF"
|
|
307
|
+
|
|
234
308
|
if cmd == "HELP":
|
|
235
309
|
self.vs.help_mode = True
|
|
236
310
|
self.vs.help_scroll = 0
|
|
@@ -285,7 +359,7 @@ class App:
|
|
|
285
359
|
except ValueError:
|
|
286
360
|
return f"Parse error: {raw}"
|
|
287
361
|
if len(orig_tokens) < 2:
|
|
288
|
-
return
|
|
362
|
+
return "Usage: EXCLUDE 'pattern' [ALL | n]"
|
|
289
363
|
pattern = orig_tokens[1]
|
|
290
364
|
rest = orig_tokens[2:]
|
|
291
365
|
if rest:
|
|
@@ -307,7 +381,7 @@ class App:
|
|
|
307
381
|
except ValueError:
|
|
308
382
|
return f"Parse error: {raw}"
|
|
309
383
|
if len(orig_tokens) < 2:
|
|
310
|
-
return
|
|
384
|
+
return "Usage: DELETE 'pattern' [ALL | n] | DELETE X ALL | DELETE NX ALL"
|
|
311
385
|
|
|
312
386
|
qualifier = orig_tokens[1].upper()
|
|
313
387
|
|
|
@@ -369,7 +443,7 @@ class App:
|
|
|
369
443
|
except ValueError:
|
|
370
444
|
return f"Parse error: {raw}"
|
|
371
445
|
if len(orig_tokens) < 3:
|
|
372
|
-
return
|
|
446
|
+
return "Usage: CHANGE 'old' 'new' [ALL] [.lbl1 .lbl2] [column]"
|
|
373
447
|
old, new = orig_tokens[1], orig_tokens[2]
|
|
374
448
|
rest = [t.upper() for t in orig_tokens[3:]]
|
|
375
449
|
# Extract optional column number — any token that is a positive integer
|
|
@@ -534,6 +608,7 @@ class App:
|
|
|
534
608
|
vs.col_offset = vs.cursor_col - text_width + 1
|
|
535
609
|
|
|
536
610
|
def _insert_char(self, ch: str) -> None:
|
|
611
|
+
self.buffer.begin_edit_group()
|
|
537
612
|
vs = self.vs
|
|
538
613
|
if not self.buffer.lines:
|
|
539
614
|
self.buffer.lines.append(__import__('notispf.buffer', fromlist=['Line']).Line(text=""))
|
|
@@ -544,6 +619,7 @@ class App:
|
|
|
544
619
|
self._scroll_col_to_cursor()
|
|
545
620
|
|
|
546
621
|
def _backspace(self) -> None:
|
|
622
|
+
self.buffer.begin_edit_group()
|
|
547
623
|
vs = self.vs
|
|
548
624
|
if not self.buffer.lines:
|
|
549
625
|
return
|
|
@@ -566,6 +642,7 @@ class App:
|
|
|
566
642
|
self._scroll_col_to_cursor()
|
|
567
643
|
|
|
568
644
|
def _delete_char(self) -> None:
|
|
645
|
+
self.buffer.begin_edit_group()
|
|
569
646
|
vs = self.vs
|
|
570
647
|
if not self.buffer.lines:
|
|
571
648
|
return
|
|
@@ -581,6 +658,7 @@ class App:
|
|
|
581
658
|
self.buffer.delete_lines(vs.cursor_line + 1, 1)
|
|
582
659
|
|
|
583
660
|
def _enter_key(self) -> None:
|
|
661
|
+
self.buffer.begin_edit_group()
|
|
584
662
|
vs = self.vs
|
|
585
663
|
if not self.buffer.lines:
|
|
586
664
|
self.buffer.lines.append(__import__('notispf.buffer', fromlist=['Line']).Line(text=""))
|
|
@@ -19,6 +19,7 @@ class Buffer:
|
|
|
19
19
|
self._undo_stack: list[list[Line]] = []
|
|
20
20
|
self._redo_stack: list[list[Line]] = []
|
|
21
21
|
self._clipboard: list[str] = []
|
|
22
|
+
self._grouping: bool = False # True while coalescing text edits
|
|
22
23
|
|
|
23
24
|
if filepath:
|
|
24
25
|
self.load_file(filepath)
|
|
@@ -34,6 +35,7 @@ class Buffer:
|
|
|
34
35
|
self.modified = False
|
|
35
36
|
self._undo_stack.clear()
|
|
36
37
|
self._redo_stack.clear()
|
|
38
|
+
self._grouping = False
|
|
37
39
|
|
|
38
40
|
def save_file(self, filepath: str | None = None) -> None:
|
|
39
41
|
target = filepath or self.filepath
|
|
@@ -50,9 +52,22 @@ class Buffer:
|
|
|
50
52
|
#-------------------------------------------------------------------
|
|
51
53
|
|
|
52
54
|
def _snapshot(self) -> None:
|
|
55
|
+
if self._grouping:
|
|
56
|
+
return
|
|
53
57
|
self._undo_stack.append(copy.deepcopy(self.lines))
|
|
54
58
|
self._redo_stack.clear()
|
|
55
59
|
|
|
60
|
+
def begin_edit_group(self) -> None:
|
|
61
|
+
"""Start a coalesced edit group. Takes one snapshot; subsequent mutations share it."""
|
|
62
|
+
if not self._grouping:
|
|
63
|
+
self._undo_stack.append(copy.deepcopy(self.lines))
|
|
64
|
+
self._redo_stack.clear()
|
|
65
|
+
self._grouping = True
|
|
66
|
+
|
|
67
|
+
def end_edit_group(self) -> None:
|
|
68
|
+
"""End the coalesced edit group so the next mutation gets its own snapshot."""
|
|
69
|
+
self._grouping = False
|
|
70
|
+
|
|
56
71
|
def undo(self) -> bool:
|
|
57
72
|
if not self._undo_stack:
|
|
58
73
|
return False
|
|
@@ -37,25 +37,31 @@ def cmd_overlay_block(buffer: Buffer, start_idx: int, end_idx: int, numeric_arg:
|
|
|
37
37
|
clipboard = buffer.pop_clipboard()
|
|
38
38
|
if not clipboard:
|
|
39
39
|
return EditorResult(success=False, message="Nothing in clipboard")
|
|
40
|
+
buffer.begin_edit_group()
|
|
40
41
|
for i, dest_idx in enumerate(range(start_idx, end_idx + 1)):
|
|
41
42
|
src = clipboard[i % len(clipboard)]
|
|
42
43
|
buffer.replace_line(dest_idx, overlay_text(src, buffer.lines[dest_idx].text))
|
|
44
|
+
buffer.end_edit_group()
|
|
43
45
|
return EditorResult(success=True, cursor_hint=start_idx)
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
def cmd_indent_right_block(buffer: Buffer, start_idx: int, end_idx: int, numeric_arg: int = 1) -> EditorResult:
|
|
47
49
|
"""Shift all lines in block right by numeric_arg columns."""
|
|
50
|
+
buffer.begin_edit_group()
|
|
48
51
|
for i in range(start_idx, end_idx + 1):
|
|
49
52
|
buffer.replace_line(i, " " * numeric_arg + buffer.lines[i].text)
|
|
53
|
+
buffer.end_edit_group()
|
|
50
54
|
return EditorResult(success=True)
|
|
51
55
|
|
|
52
56
|
|
|
53
57
|
def cmd_indent_left_block(buffer: Buffer, start_idx: int, end_idx: int, numeric_arg: int = 1) -> EditorResult:
|
|
54
58
|
"""Shift all lines in block left by numeric_arg columns, removing up to that many leading spaces."""
|
|
59
|
+
buffer.begin_edit_group()
|
|
55
60
|
for i in range(start_idx, end_idx + 1):
|
|
56
61
|
text = buffer.lines[i].text
|
|
57
62
|
leading = len(text) - len(text.lstrip(" "))
|
|
58
63
|
buffer.replace_line(i, text[min(numeric_arg, leading):])
|
|
64
|
+
buffer.end_edit_group()
|
|
59
65
|
return EditorResult(success=True)
|
|
60
66
|
|
|
61
67
|
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
"""Single-line prefix command implementations: D, I, R, C, M, A, B."""
|
|
1
|
+
"""Single-line prefix command implementations: D, I, R, C, M, A, B, HEX, HEXB."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from notispf.buffer import Buffer
|
|
4
4
|
from notispf.commands.registry import CommandRegistry, CommandSpec, EditorResult
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Hex utilities (used by prefix commands and HEX ON/OFF command line)
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
def line_to_hex(text: str) -> str:
|
|
12
|
+
"""Convert a text line to space-separated uppercase hex bytes."""
|
|
13
|
+
return ' '.join(f'{ord(c):02X}' for c in text)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def hex_to_line(hex_str: str) -> str:
|
|
17
|
+
"""Convert a space-separated hex string back to text. Raises ValueError on bad input."""
|
|
18
|
+
tokens = hex_str.split()
|
|
19
|
+
if not tokens:
|
|
20
|
+
return ''
|
|
21
|
+
return ''.join(chr(int(t, 16)) for t in tokens)
|
|
22
|
+
|
|
23
|
+
|
|
7
24
|
def cmd_delete(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
8
25
|
if line_idx >= len(buffer):
|
|
9
26
|
return EditorResult(success=False, message="Invalid line")
|
|
@@ -68,12 +85,14 @@ def cmd_overlay(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
|
68
85
|
clipboard = buffer.pop_clipboard()
|
|
69
86
|
if not clipboard:
|
|
70
87
|
return EditorResult(success=False, message="Nothing in clipboard")
|
|
88
|
+
buffer.begin_edit_group()
|
|
71
89
|
for i in range(count):
|
|
72
90
|
dest_idx = line_idx + i
|
|
73
91
|
if dest_idx >= len(buffer):
|
|
74
92
|
break
|
|
75
93
|
src = clipboard[i % len(clipboard)]
|
|
76
94
|
buffer.replace_line(dest_idx, overlay_text(src, buffer.lines[dest_idx].text))
|
|
95
|
+
buffer.end_edit_group()
|
|
77
96
|
return EditorResult(success=True, cursor_hint=line_idx)
|
|
78
97
|
|
|
79
98
|
|
|
@@ -91,6 +110,48 @@ def cmd_indent_left(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
|
91
110
|
return EditorResult(success=True)
|
|
92
111
|
|
|
93
112
|
|
|
113
|
+
def cmd_hex(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
114
|
+
"""HEX — replace this line with its hex representation."""
|
|
115
|
+
text = buffer.lines[line_idx].text
|
|
116
|
+
buffer.replace_line(line_idx, line_to_hex(text))
|
|
117
|
+
return EditorResult(success=True)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cmd_hex_below(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
121
|
+
"""HEXB — insert a hex copy of this line below it."""
|
|
122
|
+
text = buffer.lines[line_idx].text
|
|
123
|
+
buffer.insert_lines(line_idx, [line_to_hex(text)])
|
|
124
|
+
return EditorResult(success=True, cursor_hint=line_idx)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cmd_uppercase(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
128
|
+
"""UC — uppercase this line (or n lines)."""
|
|
129
|
+
buffer.begin_edit_group()
|
|
130
|
+
for i in range(line_idx, min(line_idx + count, len(buffer))):
|
|
131
|
+
buffer.replace_line(i, buffer.lines[i].text.upper())
|
|
132
|
+
buffer.end_edit_group()
|
|
133
|
+
return EditorResult(success=True)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def cmd_lowercase(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
137
|
+
"""LC — lowercase this line (or n lines)."""
|
|
138
|
+
buffer.begin_edit_group()
|
|
139
|
+
for i in range(line_idx, min(line_idx + count, len(buffer))):
|
|
140
|
+
buffer.replace_line(i, buffer.lines[i].text.lower())
|
|
141
|
+
buffer.end_edit_group()
|
|
142
|
+
return EditorResult(success=True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def cmd_hex_to_ascii(buffer: Buffer, line_idx: int, count: int) -> EditorResult:
|
|
146
|
+
"""HEXA — convert a hex line back to its ASCII text representation."""
|
|
147
|
+
text = buffer.lines[line_idx].text
|
|
148
|
+
try:
|
|
149
|
+
buffer.replace_line(line_idx, hex_to_line(text))
|
|
150
|
+
except ValueError:
|
|
151
|
+
return EditorResult(success=False, message="HEXA: line is not valid hex")
|
|
152
|
+
return EditorResult(success=True)
|
|
153
|
+
|
|
154
|
+
|
|
94
155
|
def register(registry: CommandRegistry) -> None:
|
|
95
156
|
registry.register_line_cmd(CommandSpec("D", cmd_delete, description="Delete line(s)"))
|
|
96
157
|
registry.register_line_cmd(CommandSpec("I", cmd_insert, description="Insert blank line(s)"))
|
|
@@ -102,3 +163,8 @@ def register(registry: CommandRegistry) -> None:
|
|
|
102
163
|
registry.register_line_cmd(CommandSpec("O", cmd_overlay, description="Overlay clipboard onto this line"))
|
|
103
164
|
registry.register_line_cmd(CommandSpec(">", cmd_indent_right, description="Indent line right n columns"))
|
|
104
165
|
registry.register_line_cmd(CommandSpec("<", cmd_indent_left, description="Indent line left n columns"))
|
|
166
|
+
registry.register_line_cmd(CommandSpec("HEX", cmd_hex, description="Replace line with hex representation"))
|
|
167
|
+
registry.register_line_cmd(CommandSpec("HEXB", cmd_hex_below, description="Insert hex copy of line below"))
|
|
168
|
+
registry.register_line_cmd(CommandSpec("HEXA", cmd_hex_to_ascii, description="Convert hex line back to ASCII"))
|
|
169
|
+
registry.register_line_cmd(CommandSpec("UC", cmd_uppercase, description="Uppercase line(s)"))
|
|
170
|
+
registry.register_line_cmd(CommandSpec("LC", cmd_lowercase, description="Lowercase line(s)"))
|
|
@@ -37,6 +37,7 @@ class ViewState:
|
|
|
37
37
|
highlight_pattern: str = "" # pattern to highlight (empty = none)
|
|
38
38
|
help_mode: bool = False # True when help screen is visible
|
|
39
39
|
help_scroll: int = 0 # top line of help screen
|
|
40
|
+
hex_mode: bool = False # True when HEX ON is active
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
# Layout constants
|
|
@@ -93,8 +94,9 @@ class Display:
|
|
|
93
94
|
def _render_status(self, buffer, vs: ViewState, cols: int) -> None:
|
|
94
95
|
filename = buffer.filepath or "[No File]"
|
|
95
96
|
modified = " [+]" if buffer.modified else ""
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
hex_ind = " [HEX]" if vs.hex_mode else ""
|
|
98
|
+
position = f" Line {vs.cursor_line + 1}/{len(buffer)} Col {vs.cursor_col + 1}"
|
|
99
|
+
left = f" notispf {filename}{modified}{hex_ind}"
|
|
98
100
|
right = position + " "
|
|
99
101
|
padding = cols - len(left) - len(right)
|
|
100
102
|
if padding < 0:
|
|
@@ -270,19 +272,23 @@ class Display:
|
|
|
270
272
|
_HELP_LINES = [
|
|
271
273
|
" notispf — Help Press any key to exit",
|
|
272
274
|
"",
|
|
273
|
-
" COMMAND LINE (press
|
|
275
|
+
" COMMAND LINE (press F6 to open)",
|
|
274
276
|
" " + "─" * 60,
|
|
275
277
|
" SAVE Save file",
|
|
276
278
|
" FILE Save and exit",
|
|
277
279
|
" CANCEL / QUIT Exit without saving",
|
|
278
280
|
" COPY filename Copy buffer to another file",
|
|
279
|
-
" FIND
|
|
280
|
-
" CHANGE
|
|
281
|
-
" EXCLUDE
|
|
281
|
+
" FIND 'pat' [col] Find next occurrence (col = start column)",
|
|
282
|
+
" CHANGE 'o' 'n' [opts] Change text (opts: ALL, col, .lbl .lbl)",
|
|
283
|
+
" EXCLUDE 'pat' [ALL|n] Exclude matching lines from view",
|
|
282
284
|
" SHOW ALL Un-exclude all lines",
|
|
283
|
-
" DELETE
|
|
285
|
+
" DELETE 'pat' [ALL|n] Delete lines matching pattern",
|
|
284
286
|
" DELETE X ALL Delete all excluded lines",
|
|
285
287
|
" DELETE NX ALL Delete all non-excluded lines",
|
|
288
|
+
" UNDO Undo last change",
|
|
289
|
+
" REDO Redo last undone change",
|
|
290
|
+
" HEX ON Convert entire file to hex display",
|
|
291
|
+
" HEX OFF Convert hex display back to text",
|
|
286
292
|
" COLS Toggle column ruler",
|
|
287
293
|
" CLEAR Clear search/change highlighting",
|
|
288
294
|
" HELP Show this screen",
|
|
@@ -301,11 +307,16 @@ class Display:
|
|
|
301
307
|
" S / Sn Show (un-exclude) SS Show block",
|
|
302
308
|
" >n Indent right n cols >>n Indent block right n cols",
|
|
303
309
|
" <n Indent left n cols <<n Indent block left n cols",
|
|
310
|
+
" HEX Replace line with hex HEXB Insert hex copy below",
|
|
311
|
+
" HEXA Convert hex line to ASCII",
|
|
312
|
+
" UC / UCn Uppercase line(s) LC / LCn Lowercase line(s)",
|
|
304
313
|
"",
|
|
305
314
|
" FUNCTION KEYS",
|
|
306
315
|
" " + "─" * 60,
|
|
307
|
-
" F3
|
|
308
|
-
"
|
|
316
|
+
" F1 Help F3 Save and exit",
|
|
317
|
+
" F5 Repeat last FIND",
|
|
318
|
+
" F6 Open command line F12 Exit without saving",
|
|
319
|
+
" Ctrl+Z Undo Ctrl+Y Redo",
|
|
309
320
|
" F7 / PgUp Scroll up F8/PgDn Scroll down",
|
|
310
321
|
" F10 Scroll left F11 Scroll right",
|
|
311
322
|
" Tab Move to prefix area",
|
|
@@ -64,6 +64,7 @@ class FindChangeEngine:
|
|
|
64
64
|
needle = old if case_sensitive else old.lower()
|
|
65
65
|
required_col = (col - 1) if col is not None else None
|
|
66
66
|
|
|
67
|
+
self.buffer.begin_edit_group()
|
|
67
68
|
for i, line in enumerate(self.buffer.lines):
|
|
68
69
|
if line.excluded:
|
|
69
70
|
continue
|
|
@@ -80,6 +81,7 @@ class FindChangeEngine:
|
|
|
80
81
|
else text.replace(old, new)
|
|
81
82
|
self.buffer.replace_line(i, new_text)
|
|
82
83
|
count += haystack.count(needle)
|
|
84
|
+
self.buffer.end_edit_group()
|
|
83
85
|
return count
|
|
84
86
|
|
|
85
87
|
def change_in_range(self, old: str, new: str,
|
|
@@ -99,6 +101,7 @@ class FindChangeEngine:
|
|
|
99
101
|
needle = old if case_sensitive else old.lower()
|
|
100
102
|
required_col = (col - 1) if col is not None else None
|
|
101
103
|
|
|
104
|
+
self.buffer.begin_edit_group()
|
|
102
105
|
for i in range(start_idx, end_idx + 1):
|
|
103
106
|
if self.buffer.lines[i].excluded:
|
|
104
107
|
continue
|
|
@@ -115,6 +118,7 @@ class FindChangeEngine:
|
|
|
115
118
|
else text.replace(old, new)
|
|
116
119
|
self.buffer.replace_line(i, new_text)
|
|
117
120
|
count += haystack.count(needle)
|
|
121
|
+
self.buffer.end_edit_group()
|
|
118
122
|
return count
|
|
119
123
|
|
|
120
124
|
|
|
@@ -138,15 +142,19 @@ class FindChangeEngine:
|
|
|
138
142
|
def delete_excluded(self) -> int:
|
|
139
143
|
"""Delete all excluded lines. Returns count of lines deleted."""
|
|
140
144
|
to_delete = [i for i, line in enumerate(self.buffer.lines) if line.excluded]
|
|
145
|
+
self.buffer.begin_edit_group()
|
|
141
146
|
for i in reversed(to_delete):
|
|
142
147
|
self.buffer.delete_lines(i, 1)
|
|
148
|
+
self.buffer.end_edit_group()
|
|
143
149
|
return len(to_delete)
|
|
144
150
|
|
|
145
151
|
def delete_non_excluded(self) -> int:
|
|
146
152
|
"""Delete all non-excluded lines. Returns count of lines deleted."""
|
|
147
153
|
to_delete = [i for i, line in enumerate(self.buffer.lines) if not line.excluded]
|
|
154
|
+
self.buffer.begin_edit_group()
|
|
148
155
|
for i in reversed(to_delete):
|
|
149
156
|
self.buffer.delete_lines(i, 1)
|
|
157
|
+
self.buffer.end_edit_group()
|
|
150
158
|
return len(to_delete)
|
|
151
159
|
|
|
152
160
|
def delete_matching(self, pattern: str, limit: int | None = None,
|
|
@@ -165,9 +173,10 @@ class FindChangeEngine:
|
|
|
165
173
|
break
|
|
166
174
|
|
|
167
175
|
# Delete in reverse order so indices stay valid
|
|
176
|
+
self.buffer.begin_edit_group()
|
|
168
177
|
for i in reversed(to_delete):
|
|
169
178
|
self.buffer.delete_lines(i, 1)
|
|
170
|
-
|
|
179
|
+
self.buffer.end_edit_group()
|
|
171
180
|
return len(to_delete)
|
|
172
181
|
|
|
173
182
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: notispf
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: A terminal text editor inspired by the ISPF editor from z/OS
|
|
5
5
|
Author-email: mrthock <m64357@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,7 +77,7 @@ notispf <file>
|
|
|
77
77
|
## Screen Layout
|
|
78
78
|
|
|
79
79
|
```
|
|
80
|
-
notispf filename.txt
|
|
80
|
+
notispf filename.txt Line 1/42 Col 1
|
|
81
81
|
000001|This is the first line of your file
|
|
82
82
|
000002|Second line here
|
|
83
83
|
000003|Third line
|
|
@@ -86,7 +86,7 @@ notispf <file>
|
|
|
86
86
|
Type prefix command, Enter to execute, Esc to cancel
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
- **Status bar** (top) — filename, modified flag `[+]`, current line/total
|
|
89
|
+
- **Status bar** (top) — filename, modified flag `[+]`, current line/total, cursor column, `[HEX]` when hex mode is active
|
|
90
90
|
- **Prefix column** (left, 6 chars) — shows line numbers; type commands here
|
|
91
91
|
- **Text area** (right of `|`) — edit your file
|
|
92
92
|
- **Message/command line** (bottom) — status messages and command input
|
|
@@ -132,6 +132,13 @@ You can stage commands on multiple lines before pressing Enter — they all exec
|
|
|
132
132
|
| `B` | Paste clipboard **before** this line |
|
|
133
133
|
| `>n` | Indent right n columns (e.g. `>4` adds 4 spaces) |
|
|
134
134
|
| `<n` | Indent left n columns (removes up to n leading spaces) |
|
|
135
|
+
| `HEX` | Replace this line with its hex representation |
|
|
136
|
+
| `HEXB` | Insert a hex copy of this line below it |
|
|
137
|
+
| `HEXA` | Convert a hex line back to ASCII text |
|
|
138
|
+
| `UC` | Uppercase this line |
|
|
139
|
+
| `UCn` | Uppercase n lines (e.g. `UC3`) |
|
|
140
|
+
| `LC` | Lowercase this line |
|
|
141
|
+
| `LCn` | Lowercase n lines |
|
|
135
142
|
|
|
136
143
|
### Block Commands
|
|
137
144
|
|
|
@@ -164,7 +171,7 @@ Use `A` or `B` on a third line to place the clipboard after copying or moving.
|
|
|
164
171
|
|
|
165
172
|
## Command Line
|
|
166
173
|
|
|
167
|
-
Press
|
|
174
|
+
Press **F6** to open the command line, then type a command and press Enter.
|
|
168
175
|
|
|
169
176
|
### File Commands
|
|
170
177
|
|
|
@@ -172,17 +179,37 @@ Press **`=`** or **`F6`** to open the command line, then type a command and pres
|
|
|
172
179
|
|---------|--------|
|
|
173
180
|
| `SAVE` | Save file |
|
|
174
181
|
| `FILE` | Save and exit |
|
|
175
|
-
| `CANCEL` or `QUIT` | Exit without saving |
|
|
182
|
+
| `CANCEL` or `QUIT` or `CAN` | Exit without saving |
|
|
183
|
+
|
|
184
|
+
### Undo / Redo
|
|
185
|
+
|
|
186
|
+
| Command | Action |
|
|
187
|
+
|---------|--------|
|
|
188
|
+
| `UNDO` | Undo last change |
|
|
189
|
+
| `REDO` | Redo last undone change |
|
|
190
|
+
|
|
191
|
+
### Hex Mode
|
|
192
|
+
|
|
193
|
+
| Command | Action |
|
|
194
|
+
|---------|--------|
|
|
195
|
+
| `HEX ON` | Convert entire file to hex (e.g. `Hello` → `48 65 6C 6C 6F`) |
|
|
196
|
+
| `HEX OFF` | Convert hex back to text |
|
|
197
|
+
|
|
198
|
+
`[HEX]` appears in the status bar while hex mode is active. `HEX ON` and `HEX OFF` each count as a single undo step.
|
|
176
199
|
|
|
177
200
|
### Find and Change
|
|
178
201
|
|
|
179
202
|
```
|
|
180
|
-
FIND
|
|
181
|
-
CHANGE
|
|
182
|
-
CHANGE
|
|
183
|
-
CHANGE
|
|
203
|
+
FIND 'text'
|
|
204
|
+
CHANGE 'old' 'new'
|
|
205
|
+
CHANGE 'old' 'new' ALL
|
|
206
|
+
CHANGE 'old' 'new' ALL .labelA .labelB
|
|
184
207
|
```
|
|
185
208
|
|
|
209
|
+
Both single and double quotes are accepted as delimiters.
|
|
210
|
+
|
|
211
|
+
Aliases: `F` for `FIND`, `C` for `CHANGE`.
|
|
212
|
+
|
|
186
213
|
- `FIND` — locate next occurrence (case-insensitive by default)
|
|
187
214
|
- `CHANGE` — replace next occurrence
|
|
188
215
|
- `CHANGE ... ALL` — replace all occurrences in file
|
|
@@ -197,10 +224,17 @@ Labels are used to define ranges for `CHANGE ... ALL .A .B`.
|
|
|
197
224
|
|
|
198
225
|
| Key | Action |
|
|
199
226
|
|-----|--------|
|
|
227
|
+
| F1 | Help |
|
|
200
228
|
| F3 | Save and exit |
|
|
201
|
-
| F5 |
|
|
229
|
+
| F5 | Repeat last FIND (RFIND) |
|
|
202
230
|
| F6 | Open command line |
|
|
231
|
+
| F7 / Page Up | Scroll up |
|
|
232
|
+
| F8 / Page Down | Scroll down |
|
|
233
|
+
| F10 | Scroll left |
|
|
234
|
+
| F11 | Scroll right |
|
|
203
235
|
| F12 | Exit without saving |
|
|
236
|
+
| Ctrl+Z | Undo last change |
|
|
237
|
+
| Ctrl+Y | Redo last undone change |
|
|
204
238
|
|
|
205
239
|
## Contributing
|
|
206
240
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.0"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|