v0.1.0-b217 (alpha) · Apple Silicon · macOS 13.2+ · 更新日志
本地优先
无需账号,无需云端,无遥测。
Scriptable
Automate repetitive work with Python and editor commands.
22960 # EditorContainer : _on_tab_label_clicked
22961 @func_trace
22962 def _on_tab_label_clicked(self):
22963 editor = ensure_not_deleted(self.main_editor.editor)
22964 if editor is not None:
22965 safe_focus(editor)
22966
22967 # EditorContainer : _on_tab_label_toggle
22968 @func_trace
22969 def _on_tab_label_toggle(self):
22970 window = ensure_not_deleted(self.window)
22971 if window is None:
22972 return
22973
22974 buffer = self.buffer
22975 if buffer is None:
22976 return
22977
22978 window.buffer_manager.toggle_active(buffer)
22979
22980 # EditorContainer : _on_tab_label_exclusive
22981 @func_trace
22982 def _on_tab_label_exclusive(self):
22983 window = ensure_not_deleted(self.window)
22984 if window is None:
22985 return
22986
22987 buffer = self.buffer
22988 if buffer is None:
22989 return
22990
22991 window.buffer_manager.set_as_active_tab(buffer)
22992
22993 # EditorContainer : _file_changed
22994 @func_trace
22995 def _file_changed(self, *_): [97 lines folded]
23093
23094 # EditorContainer : _do_after_file_changed
23095 @func_trace
23096 def _do_after_file_changed(self): [5 lines folded]
10373 directory = os.path.dirname(path) or "."
10374 tmp_path = None
10375
10376 os.makedirs(directory, exist_ok=True)
10377
10378 fd, tmp_path = tempfile.mkstemp(dir=directory, prefix=".tmp_")
10379
10380 try:
10381 with os.fdopen(fd, "w", encoding="utf-8") as f:
10382 f.write(text)
10383 f.flush()
10384 os.fsync(f.fileno())
10385
10386 os.replace(tmp_path, path)
10387 tmp_path = None
10388
10389 finally:
10390 if tmp_path is not None:
10391 try:
10392 os.remove(tmp_path)
10393 except OSError:
10394 pass
10395
10396 # CodeEditor : _save_file
10397 # @func_trace
10398 def _save_file(self): [113 lines folded]
10512
10513 # CodeEditor : _save_shadow_file
10514 @func_trace
10515 def _save_shadow_file(self): [18 lines folded]
10534
10535 # CodeEditor : save_file_as
10536 @plugin_api
10537 @command("Save file as...")
10538 def save_file_as(self): [20 lines folded]
10559
10560 # CodeEditor : save_file_copy_as
10561 @plugin_api
10562 @command("Save copy of file as...")
10563 def save_file_copy_as(self): [20 lines folded]
Everything included out of the box.
Context-aware autocomplete
Complete words from the current file, with suggestions ranked by nearby context and usage.
自定义编辑器命令
无需学习插件框架,直接使用普通 Python 扩展 Koi。
Inline commands
Run shell commands directly from the document with ⌘⇧⏎.
Use % for shell, > for chat, or evaluate Python directly.
Settings are plain text
Configuration and key bindings stay readable and editable.
See default config file →Built-in language support
Support for 30+ programming languages out of the box, with more planned.
Roadmap →Find anything
Go to any file with ⌘P, search everywhere with ⌘⇧F.
Koi is built around a native C++ editing core, with responsiveness treated as a constraint rather than an optimization added later.
Editing stays responsive as files grow, with multiple views of the same document sharing a single underlying buffer.
Performance is measured on real source files and published with reproducible benchmarks.
172 bool isFunctionLike = false;
173
174 // keywords
175 if (keywords.InList(s)) {
176 sc.ChangeState(SCE_HACK_KEYWORD);
177 }
178 // types
179 else if (types.InList(s)) {
180 sc.ChangeState(SCE_HACK_TYPE);
181 }
182 // constants
183 else if (constants.InList(s)) {
184 sc.ChangeState(SCE_HACK_CONSTANT);
185 }
186 // builtins
187 else if (builtins.InList(s)) {
188 sc.ChangeState(SCE_HACK_BUILTIN);
189 }
190 else {
191 const Sci_Position line = styler.GetLine(sc.currentPos);
192 const Sci_PositionU lineEnd = styler.LineEnd(line);
193 Sci_PositionU pos = sc.currentPos>;
194
195 while (pos < lineEnd) {
196 const unsigned char c = styler.SafeGetCharAt(pos);
197 if (c == ' ' || c == '\t') {
198 ++pos;
199 continue;
200 }
201 isFunctionLike = (c == '(');
202 break;
203 }
267 // enter new state
268
269 if (sc.state == SCE_HACK_DEFAULT) {
270 // numbers
271 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
272 sc.SetState(SCE_HACK_NUMBER);
273 }
274 // identifiers
275 else if (!sc.atLineEnd && (setWordStart.Contains(sc.ch) || (sc.ch == '@'))) {
276 sc.SetState(State_Identifier);
277 }
278 // block comment
279 else if (sc.Match('/', '*')) {
280 sc.SetState(State_BlockComment);
281 sc.Forward();
282 }
283 // line comments
284 else if (sc.Match('/', '/')) {
285 sc.SetState(State_LineComment);
286 }
287 // strings
288 else if (sc.ch == '\"') {
289 sc.SetState(SCE_HACK_STRING);
290 isIncludePreprocessor = false;
291 }
292 // includes
293 else if (isIncludePreprocessor && sc.ch == '<') {
294 sc.SetState(SCE_HACK_STRING);
295 }
296 // characters
297 else if (sc.ch == '\'') {
298 sc.SetState(State_Character);
Ultra low-latency typing
16.6 ms P95 typing latency on a 753,821-line source file. Fastest of all compared editors.
Benchmarks →Multiple views, one document
Work in multiple views of the same document without sacrificing editing responsiveness.
Multiple views →Handles very large files
Edit syntax-highlighted files with millions of lines smoothly and responsively.
Large file performance →Measured, not claimed
16.6 ms
P95 typing latency on a 753,821-line source file.
20M lines
Largest syntax-highlighted file tested.
698 MB
Memory usage at 5 million lines source file.
Useful features without sending your code somewhere else. Koi works directly with local files, local indexes, and local models.
Context-aware autocomplete is built from the current file, without requiring a language server. AI features run locally through Ollama and are disabled by default.
Your files stay on your machine, and the editor remains fully functional offline.
15 types
16 int
17 float
18 arguments
19 out : stdin to use previous output as input
20 : stdout to print
21 """
22
23 program = """
24 Module add 1 2 3 type=float out=stdin
25 Module sub stdin 1 2 type=int out=stdout
26 """
27
28 # Module definition
29 class Module:
30
31 # Add function
32 def add(*args, **kwargs):
33 type_ = globals()["__builtins__"].getattr(globals()["__builtins__"], kwargs["type"])
34 return sum(map(type_, args))
35
36 # Sub function
37 def sub(*args, **kwargs):
38 type_ = globals()["__builtins__"].getattr(globals()["__builtins__"], kwargs["type"])
39 return float(args[0]) if type_ == 'float' else int(args[0]) - sum(map(type_, args[1:]))
40
41 def get_args(tokens, STDIN): [18 lines folded]
60
61 def interpret(program): [25 lines folded]
87
88 interpret(program) # 3
89
Context-aware autocomplete
Complete words from the current file, ranked by nearby context and usage.
Context-aware autocomplete →Local-first AI
Run inline chat and code completion locally with your own models through Ollama.
Local AI →No telemetry
No accounts, usage tracking, or analytics. Koi works fully offline with your files.
Privacy Policy →Privacy by design
No telemetry or tracking. No background downloads. Not even an auto-updater.
Offline mode not required.
Koi exposes editor commands to Python, so repetitive work can become part of the editor instead of another plugin or external tool.
Write ordinary Python functions, expose them as commands, assign keybindings, and use them just like the editor's built-in commands.
Inline commands bring the same idea into the document: run shell commands, evaluate Python, or use AI without leaving the editor.
1 % git log --oneline -5
2 7e43c91 Fix stale index results after file changes
3 20b9ad4 Add PHP symbols to file outline
4 e3816fc Preserve cursor position when reopening files
5 9ad0c72 Reduce allocations while indexing large files
6 615bc84 Fix split view restoring the wrong document
7
8 > Summarize these changes for the changelog.
9
10 ## Editor
11
12 - Added PHP symbols to the file outline.
13 - Cursor position is now preserved when reopening files.
14 - Fixed split views occasionally restoring the wrong document.
15
16 ## Performance and reliability
17
18 - Reduced memory allocations while indexing large files.
19 - Fixed stale autocomplete results after files changed on disk.
20
21 % screenfetch -N
22
23 -/+:. michael@Michaels-Mac-mini
24 :++++. OS: 64bit macOS
25 /+++/. Kernel: arm64 Darwin 24.6.0
26 .:-::- .+/:-``.::- Uptime: 2d 3h 24m
27 .:/++++++/::::/++++++/:` Packages: 76
28 .:///////////////////////:` Shell: zsh 5.9
29 ////////////////////////` Resolution: 3440x1440
30 -+++++++++++++++++++++++` DE: Aqua
31 /++++++++++++++++++++++/ WM: Quartz Compositor
32 /sssssssssssssssssssssss. WM Theme: Graphite (Dark)
1 2 # This is the default scripts-file for Koi Editor 3 4 @command("Word count") 5 def word_count(): 6 import re 7 8 text = selected_text() 9 count = len(re.findall(r"\b[\w'-]+\b", text)) 10 11 message (f"Word count: {count:,} words") 12 13 @command("Insert UUID") 14 def insert_uuid(): [2 lines folded] 17 18 @command("Markdown: Selection to link") 19 def insert_link(): [2 lines folded] 22 23 @command("Markdown: Insert unchecked box") 24 def insert_unchecked_box(): [1 lines folded] 26 27 @command("Markdown: Wrap selection in fenced block") 28 def fenced_block(): [2 lines folded] 31 32 @command("Markdown: Bold") 33 def make_bold(): 34 text = selected_text() 35 insert_text(text=f"**{text}**") 36 37 38 39
Python scripting
Write editor commands as ordinary Python functions and bind them to keys or run them from the command palette.
Explore the API →Inline commands
Run shell commands with %, inline chat with >, or evaluate Python directly from the document.
Shell and Python eval →Plain-text configuration
Configure settings, keybindings, models, and editor behavior in files you can inspect, edit, and version.
Documentation →Zero-latency typing
Koi maintains a measured P95 typing latency of 17.86 ms while editing a 1 million-line Odin source file with syntax highlighting enabled.
Local AI, on your Mac
Koi works directly with local Ollama models, with no account, telemetry, or cloud intermediary. Use local code completion and inline AI while keeping your code and inference on your Mac.
Native macOS code editors
A look at code editors built specifically for macOS, from long-running editors like BBEdit and TextMate to newer alternatives like Nova, CodeEdit, and Koi.
Note taking and organization
Keep notes as ordinary Markdown, plain text, Org Mode, or other files. Open directly into your notes folder, search with ripgrep, and organize everything without a proprietary database.
Writer mode with focused interface
Variable fonts and wikilinks
File preview in file explorer
Faster search in very large files
One command palette for all dialogs
手工调校的原生词法分析器,带来快速、精确的语法高亮。
Code folding is syntax-aware, allowing for language-specific behavior.
Some languages have file outline support, enabling navigation through the code via symbols.
Asm
Bash
C
CSS
Clojure
C++
D
Diff
Elixir
Fortran
Gleam
Go
Haskell
HTML
Jai
Java
JavaScript
Julia
JSON
LaTeX
Lua
Makefile
Markdown
MATLAB
Mojo
Nim
OCaml
Odin
Org
Pascal
Perl
PHP
Python
R
Ruby
Rust
SQL
Swift
TOML
TypeScript
Typst
WAT
YAML
Zig
手工调校的原生词法分析器,带来快速、精确的语法高亮。
Minimal syntax highlighting for better performance. The lexers are written in C++ and optimized for speed.
Code folding is syntax-aware, allowing for language-specific behavior.
Some languages have file outline support, enabling navigation through the code via symbols.
Asm
Bash
C
CSS
Clojure
C++
D
Diff
Elixir
Fortran
Gleam
Go
Haskell
HTML
Jai
Java
JavaScript
Julia
JSON
LaTeX
Lua
Makefile
Markdown
MATLAB
Mojo
Nim
OCaml
Odin
Org
Pascal
Perl
PHP
Python
R
Ruby
Rust
SQL
Swift
TOML
TypeScript
Typst
WAT
YAML
Zig
许可证 FAQ。
Free for non-commercial use. Commercial use requires a license.
为什么需要购买许可证?
Koi Editor 可免费用于非商业用途。如果用于工作,则需要商业许可证。这也是产品持续开发的资金来源。
什么算非商业用途?
非商业用途包括学习、个人项目、兴趣项目和试用评估。如果你将 Koi Editor 用于有偿工作、公司工作、客户项目或任何其他商业活动,则需要商业许可证。
我应该选择哪种许可证?
Personal 许可证适合单个个人用户。Teams 和 Business 许可证适合组织和多用户使用。
独自用于专业工作:Personal
用一个许可证覆盖较大的组织:Business
Personal 许可证可以用于工作吗?
可以。只要使用该软件的只有你一个人,Personal 许可证就可以用于商业工作。多人使用请选择 Teams 或 Business。
许可证文件丢失了,如何重新获取?
你可以使用购买后显示的 Session ID 恢复许可证文件。
我们也可以帮你恢复许可证。请发送邮件到 koi@hackerman.ai,并附上与购买相关的信息,例如邮箱、姓名、公司名称以及大致购买日期和时间。
Debugging race condition that was actually a lifetime bug
A random QThread crash looked like a race condition, but turned out to be an orphaned editor buffer and delayed garbage collection.
Next steps for navigation
Rethinking editor navigation around lightweight histories, on-demand search, and local structure.
Redesigning the title bar
Moving from a custom title bar to native AppKit, and figuring out what to do with tabs along the way.
Try Koi on your own code.
Free for non-commercial use. Requires macOS on Apple Silicon.