From ec214b04bf23ab929e5185721b106dafdc3d4919 Mon Sep 17 00:00:00 2001 From: Wander_Lust Date: Fri, 17 Apr 2026 02:21:00 +0530 Subject: [PATCH] waguri --- README.md | 63 ++++++++++++++ cli.py | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 README.md create mode 100644 cli.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd72363 --- /dev/null +++ b/README.md @@ -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 + + +--- + diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..361096c --- /dev/null +++ b/cli.py @@ -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()