This commit is contained in:
2026-04-17 02:21:00 +05:30
commit ec214b04bf
2 changed files with 311 additions and 0 deletions
+63
View File
@@ -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
---
+248
View File
@@ -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()