- मौजूदा Howl एडिटर की सीमाओं (डेवलपमेंट बंद, धीमी search, SSH incompatibility, terminal support की कमी) के कारण लेखक ने खुद एक नया TUI टेक्स्ट एडिटर बनाया
- Helix, VS Code, Vim, Neovim, Emacs सहित 13 एडिटर आज़माने के बाद भी ऐसा कोई एडिटर नहीं मिला जो मनचाहा ऑपरेशन फील (Fingerspitzengefühl) दे सके
- शुरुआत में केवल व्यक्तिगत रूप से ज़रूरी न्यूनतम फीचर लागू किए गए, और performance, Unicode, multilingual support जैसी चीज़ों को बाद के लिए टालते हुए धीरे-धीरे विस्तार किया गया
- डेवलपमेंट के दौरान अपना regex engine, file browser, TUI-based rendering, terminal buffer integration आदि सीधे खुद लागू किए गए
- project-wide search, syntax highlighting, cache handling, multi-threaded work distribution आदि में performance optimization techniques व्यापक रूप से लागू की गईं
- नतीजतन, लेखक ने बताया कि उसने ऐसा टूल पूरा किया जो उसके अपने workflow पर पूरी तरह फिट बैठता है, और इससे productivity और programming का आनंद फिर से मिल गया
मौजूदा एडिटर की सीमाएँ और विकल्पों की तलाश
- लगभग 10 साल तक इस्तेमाल किए गए Howl एडिटर की समस्याएँ ही खुद एडिटर बनाने की वजह बनीं
- कई सालों से डेवलपमेंट रुका हुआ था, इसलिए लेखक खुद उसका fork बनाए हुए था, लेकिन वह MoonScript में लिखा होने के कारण गहराई से बदलाव करना मुश्किल था
- पूरे project में file search performance कमजोर होने से workflow बार-बार टूटता था
- यह एक GUI एडिटर था, इसलिए SSH connection के जरिए remote use संभव नहीं था
- integrated terminal न होने से external commands चलाते समय live interaction संभव नहीं था, और ज़्यादातर ANSI escape codes भी unsupported थे
- Helix, VS Code, Sublime Text, Vim, Zed, Neovim, Emacs, Geany, Micro, Lite XL, Lapce, GNOME Builder, Kakoune सहित 13 एडिटर आज़माए गए
- हर एक की अपनी खूबियाँ थीं, लेकिन कोई भी मनचाहा ऑपरेशन फील (Fingerspitzengefühl) नहीं दे पाया
- Helix का सबसे लंबे समय तक इस्तेमाल किया गया, लेकिन एक महीने बाद उसमें भी रुचि खत्म हो गई
शुरुआती डेवलपमेंट रणनीति
- शुरुआत में scope को न्यूनतम रखा गया
- लेखक के अलावा किसी और user के लिए फीचर नहीं जोड़े गए, और सारी settings hardcode की गईं
- performance optimization को टालकर
String-based buffer से शुरुआत की गई
- Unicode grapheme के पूर्ण support को छोड़ा गया; यह मान लिया गया कि
£ चिन्ह अगर एक single column लेता है तो वही काफी है
- syntax highlighting सिर्फ उन कुछ languages के लिए दी गई जो अक्सर इस्तेमाल होती थीं, बाकी के लिए generic delimiter-based highlighting इस्तेमाल की गई
- दूसरी कोशिश में पहले एक साधारण TUI framework बनाया गया, लेकिन समय के साथ उसका अधिकांश हिस्सा हटाकर अधिक सीधा और सूक्ष्म तरीका अपनाया गया
Dogfooding का अभ्यास
- जब एडिटर इतना सक्षम हो गया कि वह एक single file खोल, edit और save कर सके, तब तीन अभ्यास शुरू किए गए
nano की जगह system files edit करने या notes लिखने में अपने ही एडिटर का जबरन इस्तेमाल
- कोई missing feature, bug, अजीब behavior या limitation दिखते ही project के
README.md में दर्ज करना
- जो समस्याएँ बहुत irritate करें, उन्हें तुरंत ठीक करना
- इन तीन आदतों से काम का समय महीने के 1 घंटे से बढ़कर हर हफ्ते कई घंटे हो गया
- कुल लगभग 10,000 lines code में से लगभग ज़्यादातर पिछले 6 महीनों में लिखी गईं
कर्सर नियंत्रण
- cursor manipulation लागू करने में कठिन क्षेत्र है
ctrl + shift + left जैसे key combinations users को सहज लगते हैं, लेकिन उनका logic लागू करना जटिल होता है
- मुख्य सलाह यह है कि high-level input को primitive operations के संयोजन के रूप में लागू किया जाए
- उदाहरण: word-wise backspace → word-wise cursor move + range select + delete में तोड़ना
- undo/redo लागू करते समय इन 3 क्रियाओं को एक group में बाँधना चाहिए ताकि परिणाम सहज लगे
- इससे समझ आता है कि modal editors ऐसे primitive operations सीधे user को expose क्यों करते हैं
फ़ाइल ब्राउज़र
- Howl का file browser वह निर्णायक कारण था जिसकी वजह से लेखक दूसरे एडिटर पर नहीं जा पा रहा था
- उसका तुरंत अपडेट होने वाला fuzzy filter इतना अच्छा था कि अक्सर 1–2 key presses में मनचाही file मिल जाती थी
- अगर file मौजूद न हो, तो उसे inline create किया जा सकता था
~/ टाइप करने पर home directory में अपने-आप switch हो जाता था
- main editing pane में खोली जाने वाली file का preview दिखता था
- दूसरे एडिटरों द्वारा file open समस्या को mouse dependency, GTK default dialog, filename guessing जैसे तरीकों से हल करने पर असंतोष जताया गया
- खुद लागू करने पर Levenshtein distance जैसी जटिल तकनीकों की बजाय 3 सरल मानदंड काफी निकले
- क्या path filter phrase से शुरू होता है
- क्या path उसमें phrase को शामिल करता है
- सबसे हाल का modification/access time
- case-insensitive matching की अनुमति दी गई, लेकिन case match होने पर रैंक थोड़ा ऊपर किया गया
- दसियों हज़ार files वाले project में भी 2 key presses के बाद लगभग 95% संभावना रहती थी कि मनचाही file top 2 results में हो
regex engine
- regex का उपयोग project-wide search, syntax highlighting, buffer के अंदर find—इन तीन जगहों पर होता है
- मौजूदा
regex-automata crate की जगह खुद implementation करने के कारण
- Rust raw string syntax जैसे context-sensitive edge cases संभालने की ज़रूरत
- और यह project अपना stack बनाने और समझने का अभ्यास भी था
- शुरुआती implementation में parsing crate
chumsky से regex syntax parse किया गया और हर character पर AST traverse किया जाता था, जो बहुत धीमा था
- बाद में चरणबद्ध optimization किए गए
- single-pass optimizer: बार-बार आने वाले character matching groups को एक
String node में बदलकर exact string search करना
- common prefix extraction: जैसे
hel[(lo)p] में common prefix hel निकालकर केवल उस position पर matching करना → project-wide search में बड़ा performance gain
- AST walker को Rust dynamic dispatch आधारित threaded code VM के रूप में दोबारा लागू करना
- threaded code VM को CPS (Continuation-Passing Style) में बदलना, ताकि हर VM instruction अगली instruction को tail-call करे और compiler optimization का लाभ मिले
- Rust की धीमी dynamic function calls को vtable lookup के बिना wrap करना, जिससे कई regex instructions का codegen कुछ machine instructions तक सिमट गया
- जितना संभव हो, regex instructions को Unicode codepoints की बजाय byte-level पर लागू करना; UTF-8 design के कारण ASCII optimization multi-byte codepoints पर भी कारगर रही
- jump LUT chain में compile करने की भी कोशिश की गई, लेकिन benchmark में threaded code की तुलना में सिर्फ 20–30% तेज़ी मिली और flexibility बहुत घट गई, इसलिए उसे नहीं अपनाया गया
- अंतिम परिणाम: Rust के लिए सबसे जटिल syntax highlighting में भी 50,000-line auto-generated binding file को clean state से 10 milliseconds से कम में पूरी तरह highlight किया जा सका
syntax highlighting cache
- शुरुआत में हर बदलाव पर पूरी file को फिर से highlight किया जाता था, लेकिन बड़ी files में performance गिरती थी
- इसके लिए on-demand token highlighting cache लागू किया गया
- tokens को लगभग समान आकार के chunks में highlight किया गया
- buffer में damage होने पर केवल उस स्थान से overlap करने वाले या उसके बाद वाले chunks को invalidate किया गया
- सबसे pessimistic case (बड़ी file के बीच में edit) में भी damage से पहले का highlight state बना रहता है, और screen के नीचे का हिस्सा highlight info मांगा ही नहीं जाता, इसलिए उसे process करने की ज़रूरत नहीं पड़ती
- यह demand-driven approach होने के कारण, उसी buffer के अलग-अलग हिस्से देखने वाले multiple panels में भी सही काम करती है
project-wide search
- search process के 4 चरण
- current directory से उलटी दिशा में
.git/ directory खोजकर project root तय करना
- project root की सभी directories को recursively traverse करके search pattern को file contents से match करना
- हर positive match पर file snippet निकालना और result preview के लिए syntax highlighting लागू करना
- current path से navigation distance के आधार पर results को rank करना (करीबी files को ऊँची rank)
- build directories आदि से बचने के लिए default filtering rules लागू की गईं
- multi-threaded processing के लिए basic work-stealing शैली में threads के बीच काम बाँटा गया
- ऐसी विशेष संरचना में, जहाँ सभी threads producer भी हैं और consumer भी, termination detection की समस्या हल करनी पड़ी
- idle thread atomic counter बढ़ाता है, और जब counter worker count तक पहुँच जाए और work queue खाली हो, तो पूरी processing समाप्त मानी जाती है
- regex optimization और modern SSD speed की वजह से Veloren जैसे बड़े codebase में भी simple pattern search लगभग तुरंत पूरी हो जाती है
- flamegraph में अधिकांश समय IO-bound दिखाई देता है
- एडिटर के भीतर बड़े codebase को सोच की गति से search कर पाना productivity में बहुत मददगार है
terminal emulator buffer
- pane-based editor में किसी एक pane को terminal window की तरह इस्तेमाल करने की सुविधा बहुत उपयोगी है
- लेखक ने खुद ANSI parser लिखने की कोशिश की, लेकिन OSC52, Kitty keyboard extensions जैसे आधुनिक terminal rendering features का support बहुत विशाल निकला
alacritty_terminal crate का उपयोग करके Alacritty terminal emulator के escape sequence parser और terminal state management logic को reuse किया गया
- नतीजतन, यह
screen/tmux की core functionality को बदल सकता है, और उससे भी अधिक समृद्ध escape sequence support देता है
rendering optimization
- TUI-based होने के बावजूद remote mobile connections पर bandwidth अब भी महत्वपूर्ण है
- double buffering: terminal screen की internal copies दोहरी रखी गईं
- redraw के समय previous frame से तुलना करके सिर्फ बदले हुए cells के लिए ANSI escape sequences output किए गए
- cursor movement, style mode change जैसी sequences भी सिर्फ तब output होती हैं जब सच में ज़रूरत हो
- ज़्यादातर terminal emulators (Ghostty को छोड़कर) में, एडिटर के terminal pane में बड़ी file को
cat करने के बाद एडिटर बंद करना, host terminal में सीधे cat करने से ज़्यादा तेज़ था
- क्योंकि
alacritty_terminal host terminal के stdout byte processing cost को रोक देता है
निष्कर्ष: अपना खुद का टूल बनाइए
- खुद बनाया गया एडिटर अब मेरे workflow पर पूरी तरह फिट बैठने वाला टूल बन चुका है
- लेखक इस धारणा से असहमत है कि अपना एडिटर/टूल बनाना बेकार की पीड़ा है
- चार फायदे बताए गए
- पूर्ण अनुकूलन: टूल वही करता है जो चाहिए, न उससे ज़्यादा न कम
- विविध तकनीकों की सीख: regex, ANSI, pseudoterminals (pty), TUI design, UTF-8 की बारीकियाँ आदि जैसी व्यापक रूप से उपयोगी तकनीकों की गहरी समझ मिलती है
- दीर्घकालिक productivity gain: अपने टूल को पूरी तरह समझना और अपने workflow के हिसाब से फीचर बनाना, टूल के साथ friction कम करता है
- शुद्ध आनंद: self-contained problems हल करने और उनके परिणाम को अपनी उँगलियों पर महसूस करने का अनुभव programming के प्रति प्रेम को फिर जगाता है, यहाँ तक कि वर्षों बाद कोड लिखते हुए खुलकर मुस्कुराने और अकेले हँस पड़ने जैसा अनुभव देता है
- लेखक सलाह देता है कि टेक्स्ट एडिटर ही सही नहीं, अपना कोई न कोई टूल ज़रूर बनाइए, और मुश्किल हिस्सों को statistics box (AI आदि) के हवाले करने के बजाय चुनौती का आनंद लीजिए
अभी कोई टिप्पणी नहीं है.