mirror of
https://github.com/Manoj-HV30/Mini-command-line-interface.git
synced 2026-05-16 19:35:24 +00:00
waguri
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
### 🔲 To Do
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Cursor Movement
|
||||||
|
- [x] `←` `→` — move one character at a time
|
||||||
|
- [x] `Alt+B` / `Alt+F` — jump one word left / right
|
||||||
|
- [x] `Ctrl+←` / `Ctrl+→` — same as Alt+B/F (alternate terminal sequences)
|
||||||
|
- [x] `Home` / `End` — jump to start / end of line
|
||||||
|
- [x] `Ctrl+A` / `Ctrl+E` — same as Home/End (Bash-style)
|
||||||
|
|
||||||
|
#### Editing
|
||||||
|
- [x] Printable characters — insert at cursor position, not just append
|
||||||
|
- [x] `Backspace` — delete character to the left
|
||||||
|
- [x] `Delete` — delete character to the right (forward delete)
|
||||||
|
- [x] `Alt+Backspace` / `Ctrl+W` — delete whole word to the left
|
||||||
|
- [x] `Alt+D` — delete whole word to the right
|
||||||
|
- [x] `Ctrl+K` — kill everything from cursor to end of line
|
||||||
|
- [x] `Ctrl+U` — kill everything from cursor to start of line
|
||||||
|
|
||||||
|
#### Selection
|
||||||
|
- [x] `Shift+←` / `Shift+→` — grow / shrink a text selection
|
||||||
|
- [x] `Shift+Home` / `Shift+End` — select to start / end of line
|
||||||
|
- [x] Typing while text is selected — replaces the selection
|
||||||
|
- [x] `Backspace` while text is selected — deletes the selection
|
||||||
|
|
||||||
|
#### Visual
|
||||||
|
- [x] Live trailing whitespace highlighted in red background as you type
|
||||||
|
- [x] Text selection shown with reversed colors
|
||||||
|
|
||||||
|
|
||||||
|
#### History
|
||||||
|
- [ ] `↑` / `↓` — scroll through previous commands
|
||||||
|
- [ ] `Ctrl+R` — reverse search through history (type to fuzzy-find a past command)
|
||||||
|
- [ ] Persist history to `~/.rawline_history` so it survives between sessions
|
||||||
|
- [ ] History deduplication — don't save the same command twice in a row
|
||||||
|
|
||||||
|
#### Autocomplete
|
||||||
|
- [ ] `Tab` — complete current word from a list of known commands or keywords
|
||||||
|
- [ ] Cycle through multiple matches with repeated `Tab` presses
|
||||||
|
- [ ] Dropdown menu of suggestions rendered below the input line
|
||||||
|
- [ ] Fuzzy matching — doesn't need to start with the same letters
|
||||||
|
|
||||||
|
#### Multiline Input
|
||||||
|
- [ ] `Alt+Enter` — insert a newline without submitting
|
||||||
|
- [ ] Smart indentation — auto-indent next line based on previous
|
||||||
|
|
||||||
|
#### Syntax Highlighting
|
||||||
|
- [ ] Plug in [Pygments](https://pygments.org/) for real language syntax highlighting
|
||||||
|
- [ ] Highlight matching brackets / parentheses when cursor is near one
|
||||||
|
|
||||||
|
#### More Shortcuts
|
||||||
|
- [ ] `Alt+C` — capitalize current word
|
||||||
|
- [ ] `Ctrl+Y` — yank (paste) last killed text from `Ctrl+K` or `Ctrl+U`
|
||||||
|
- [ ] `Ctrl+_` — undo last edit (with a full undo stack)
|
||||||
|
|
||||||
|
|
||||||
|
#### Clipboard
|
||||||
|
- [ ] `Ctrl+C` on a selection — copy to system clipboard
|
||||||
|
- [ ] `Ctrl+V` — paste from system clipboard
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import sys
|
||||||
|
import tty
|
||||||
|
import termios
|
||||||
|
|
||||||
|
|
||||||
|
def syntax_highlight(text, sel_start=None, sel_end=None):
|
||||||
|
if sel_start is not None and sel_end is not None and sel_start != sel_end:
|
||||||
|
s = min(sel_start, sel_end)
|
||||||
|
e = max(sel_start, sel_end)
|
||||||
|
result = text[:s] + u"\u001b[7m" + text[s:e] + u"\u001b[0m" + text[e:]
|
||||||
|
stripped = result
|
||||||
|
return stripped
|
||||||
|
stripped = text.rstrip()
|
||||||
|
trailing = len(text) - len(stripped)
|
||||||
|
if trailing > 0:
|
||||||
|
return stripped + u"\u001b[41m" + " " * trailing + u"\u001b[0m"
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def word_boundary_left(text, index):
|
||||||
|
i = index
|
||||||
|
while i > 0 and text[i - 1] == " ":
|
||||||
|
i -= 1
|
||||||
|
while i > 0 and text[i - 1] != " ":
|
||||||
|
i -= 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def word_boundary_right(text, index):
|
||||||
|
i = index
|
||||||
|
while i < len(text) and text[i] == " ":
|
||||||
|
i += 1
|
||||||
|
while i < len(text) and text[i] != " ":
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def render(prompt, prompt_len, text, index, sel_anchor):
|
||||||
|
sys.stdout.write(u"\u001b[1000D")
|
||||||
|
sys.stdout.write(u"\u001b[0K")
|
||||||
|
sys.stdout.write(prompt)
|
||||||
|
sys.stdout.write(syntax_highlight(text, sel_anchor, index))
|
||||||
|
sys.stdout.write(u"\u001b[1000D")
|
||||||
|
target = prompt_len + index
|
||||||
|
if target > 0:
|
||||||
|
sys.stdout.write(u"\u001b[" + str(target) + "C")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def read_char():
|
||||||
|
return ord(sys.stdin.read(1))
|
||||||
|
|
||||||
|
|
||||||
|
def command_line():
|
||||||
|
fd = sys.stdin.fileno()
|
||||||
|
old_settings = termios.tcgetattr(fd)
|
||||||
|
prompt = u"\u001b[32;1m>>> \u001b[0m"
|
||||||
|
prompt_len = 4
|
||||||
|
|
||||||
|
try:
|
||||||
|
tty.setraw(fd)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
text = ""
|
||||||
|
index = 0
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
sys.stdout.write(prompt)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
char = read_char()
|
||||||
|
|
||||||
|
if char == 3:
|
||||||
|
sys.stdout.write(u"\r\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif char == 4:
|
||||||
|
if not text:
|
||||||
|
sys.stdout.write(u"\r\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif char == 1:
|
||||||
|
index = 0
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif char == 5:
|
||||||
|
index = len(text)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif char == 11:
|
||||||
|
text = text[:index]
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif char == 21:
|
||||||
|
text = text[index:]
|
||||||
|
index = 0
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif char == 23:
|
||||||
|
new_index = word_boundary_left(text, index)
|
||||||
|
text = text[:new_index] + text[index:]
|
||||||
|
index = new_index
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif 32 <= char <= 126:
|
||||||
|
if sel_anchor is not None:
|
||||||
|
s = min(sel_anchor, index)
|
||||||
|
e = max(sel_anchor, index)
|
||||||
|
text = text[:s] + chr(char) + text[e:]
|
||||||
|
index = s + 1
|
||||||
|
sel_anchor = None
|
||||||
|
else:
|
||||||
|
text = text[:index] + chr(char) + text[index:]
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
elif char in {10, 13}:
|
||||||
|
sys.stdout.write(u"\u001b[1000D")
|
||||||
|
sys.stdout.write(u"\u001b[0K")
|
||||||
|
sys.stdout.write(prompt + text + u"\r\n")
|
||||||
|
sys.stdout.write(u"\u001b[36m=>\u001b[0m " + text + u"\r\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
sel_anchor = None
|
||||||
|
break
|
||||||
|
|
||||||
|
elif char == 127:
|
||||||
|
if sel_anchor is not None:
|
||||||
|
s = min(sel_anchor, index)
|
||||||
|
e = max(sel_anchor, index)
|
||||||
|
text = text[:s] + text[e:]
|
||||||
|
index = s
|
||||||
|
sel_anchor = None
|
||||||
|
elif index > 0:
|
||||||
|
text = text[:index - 1] + text[index:]
|
||||||
|
index -= 1
|
||||||
|
|
||||||
|
elif char == 27:
|
||||||
|
next1 = read_char()
|
||||||
|
|
||||||
|
if next1 == 91:
|
||||||
|
next2 = read_char()
|
||||||
|
|
||||||
|
if next2 == 68:
|
||||||
|
index = max(0, index - 1)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next2 == 67:
|
||||||
|
index = min(len(text), index + 1)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next2 == 65:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif next2 == 66:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif next2 == 72:
|
||||||
|
index = 0
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next2 == 70:
|
||||||
|
index = len(text)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next2 == 51:
|
||||||
|
next3 = read_char()
|
||||||
|
if next3 == 126:
|
||||||
|
if sel_anchor is not None:
|
||||||
|
s = min(sel_anchor, index)
|
||||||
|
e = max(sel_anchor, index)
|
||||||
|
text = text[:s] + text[e:]
|
||||||
|
index = s
|
||||||
|
sel_anchor = None
|
||||||
|
elif index < len(text):
|
||||||
|
text = text[:index] + text[index + 1:]
|
||||||
|
|
||||||
|
elif next2 == 52:
|
||||||
|
next3 = read_char()
|
||||||
|
if next3 == 126:
|
||||||
|
index = len(text)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next2 == 49:
|
||||||
|
next3 = read_char()
|
||||||
|
if next3 == 126:
|
||||||
|
index = 0
|
||||||
|
sel_anchor = None
|
||||||
|
elif next3 == 59:
|
||||||
|
next4 = read_char()
|
||||||
|
next5 = read_char()
|
||||||
|
if next4 == 50:
|
||||||
|
if next5 == 68:
|
||||||
|
if sel_anchor is None:
|
||||||
|
sel_anchor = index
|
||||||
|
index = max(0, index - 1)
|
||||||
|
elif next5 == 67:
|
||||||
|
if sel_anchor is None:
|
||||||
|
sel_anchor = index
|
||||||
|
index = min(len(text), index + 1)
|
||||||
|
elif next5 == 72:
|
||||||
|
if sel_anchor is None:
|
||||||
|
sel_anchor = index
|
||||||
|
index = 0
|
||||||
|
elif next5 == 70:
|
||||||
|
if sel_anchor is None:
|
||||||
|
sel_anchor = index
|
||||||
|
index = len(text)
|
||||||
|
elif next4 == 53:
|
||||||
|
if next5 == 68:
|
||||||
|
index = word_boundary_left(text, index)
|
||||||
|
sel_anchor = None
|
||||||
|
elif next5 == 67:
|
||||||
|
index = word_boundary_right(text, index)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next1 == 98:
|
||||||
|
index = word_boundary_left(text, index)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next1 == 102:
|
||||||
|
index = word_boundary_right(text, index)
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next1 == 100:
|
||||||
|
new_index = word_boundary_right(text, index)
|
||||||
|
text = text[:index] + text[new_index:]
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next1 == 127:
|
||||||
|
new_index = word_boundary_left(text, index)
|
||||||
|
text = text[:new_index] + text[index:]
|
||||||
|
index = new_index
|
||||||
|
sel_anchor = None
|
||||||
|
|
||||||
|
elif next1 == 98:
|
||||||
|
index = word_boundary_left(text, index)
|
||||||
|
|
||||||
|
render(prompt, prompt_len, text, index, sel_anchor)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
|
sys.stdout.write(u"\u001b[0m\r\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
command_line()
|
||||||
Reference in New Issue
Block a user