为代码而生的 文本编辑器

v0.1.0-b217 (alpha) · Apple Silicon · macOS 13.2+ · 更新日志

低延迟输入

Koi 经过优化,即使文件不断增大,也能提供可测量的低延迟输入性能。

See the benchmarks →

本地优先

无需账号,无需云端,无遥测。

Scriptable

Automate repetitive work with Python and editor commands.

koi.py
 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]
~/Desktop/koi-editor koi.py indexer.py todo.org Line 22,974, Column 29 Python

Get started

Everything included out of the box.

Flexible Workspaces

灵活的工作区

并排打开文件,将它们拆分为横向面板,或将标签页拖出到独立窗口。

Local-first AI

私有 AI 补全

通过 Ollama 在本地运行模型,让代码留在你的机器上,并选择最适合你的设备和工作流的模型。

Context-aware autocomplete

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.

A text editor, built deliberately.

Fast

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.

CLexer.cpp
  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);
~/hackerman/lexers CLexer.cpp clexer.sip Line 199, Column 22 C

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.

Benchmarks →

Local

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.

dsl.py
  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  
~/Desktop qwen2.5-coder:1.5b Line 34, Column 9 C

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.

Scriptable

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.

Untitled
   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   
~/koi-editor _ .hackerman-scripts glm-5.3-flash:cloud Generic-Default

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 →

Reasons to use Koi

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.

Read more →

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.

Read more →

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.

Read more →

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.

Read more →

路线图

我们目前正在开发以及最近发布的内容。

Changelog

Submit feature request →

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

Built-in language support

手工调校的原生词法分析器,带来快速、精确的语法高亮。

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

价格

Personal

Free

一次性付款

单用户

下载 Mac 版

Personal Early bird offer

$195 $325

一次性付款

单用户

不限设备数量

可用于商业用途

一次购买,永久拥有

购买 Personal

Teams

$795 /year

最多 10 名用户

每个许可证最多 10 名用户

每位用户不限设备数量

可用于商业用途

一年有效期

购买 Teams

Business

$19,995 /year

面向较大的组织

不限用户数量

每位用户不限设备数量

Limited custom builds

Pay by invoice

购买 Business

许可证 FAQ。

Free for non-commercial use. Commercial use requires a license.

End User License Agreement

License recovery →

为什么需要购买许可证?

Koi Editor 可免费用于非商业用途。如果用于工作,则需要商业许可证。这也是产品持续开发的资金来源。

什么算非商业用途?

非商业用途包括学习、个人项目、兴趣项目和试用评估。如果你将 Koi Editor 用于有偿工作、公司工作、客户项目或任何其他商业活动,则需要商业许可证。

我应该选择哪种许可证?

Personal 许可证适合单个个人用户。Teams 和 Business 许可证适合组织和多用户使用。

独自用于专业工作:Personal

用一个许可证覆盖较大的组织:Business

Personal 许可证可以用于工作吗?

可以。只要使用该软件的只有你一个人,Personal 许可证就可以用于商业工作。多人使用请选择 Teams 或 Business。

许可证文件丢失了,如何重新获取?

你可以使用购买后显示的 Session ID 恢复许可证文件。

我们也可以帮你恢复许可证。请发送邮件到 koi@hackerman.ai,并附上与购买相关的信息,例如邮箱、姓名、公司名称以及大致购买日期和时间。

Latest from Koi

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.

Read more →

Next steps for navigation

Rethinking editor navigation around lightweight histories, on-demand search, and local structure.

Read more →

Redesigning the title bar

Moving from a custom title bar to native AppKit, and figuring out what to do with tabs along the way.

Read more →

Koi App Icon

Try Koi on your own code.

Free for non-commercial use. Requires macOS on Apple Silicon.