This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Textbringer is an Emacs-like text editor written in Ruby. It is extensible by Ruby instead of Lisp and runs in the terminal using ncurses.
Ruby Version: Requires Ruby >= 3.2
bundle installFor ncursesw support (required for multibyte characters):
sudo apt-get install libncursesw5-dev
gem install curses# Run the main executable
./exe/txtb
# Or after installation
txtb# Run all tests
bundle exec rake test
# Run a single test file
ruby -Ilib:test test/textbringer/test_buffer.rb# Install gem locally
bundle exec rake install
# Bump version and create release
bundle exec rake bumpBuffer (lib/textbringer/buffer.rb)
- The fundamental text container, similar to Emacs buffers
- Uses a gap buffer implementation (GAP_SIZE = 256) for efficient text editing
- Supports undo/redo with UNDO_LIMIT = 1000
- Handles encoding detection (UTF-8, EUC-JP, Windows-31J) and file format conversion
- Manages marks, point (cursor position), and the kill ring
- Class methods maintain global buffer list (@@list, @@current, @@minibuffer)
Window (lib/textbringer/window.rb)
- Display abstraction using curses for terminal UI
- Multiple windows can display different buffers or the same buffer
- Window.current tracks the active window
- Echo area (@@echo_area) for messages and minibuffer input
- Manages cursor position and window splitting/deletion
Controller (lib/textbringer/controller.rb)
- The main event loop and command dispatcher
- Reads key sequences and dispatches to commands
- Handles prefix arguments, keyboard macros, and recursive editing
- Maintains command execution state (this_command, last_command)
- Pre/post command hooks for extensibility
Mode (lib/textbringer/mode.rb)
- Buffer modes define context-specific behavior and syntax highlighting
- Modes inherit from Mode class (FundamentalMode, ProgrammingMode, RubyMode, CMode, etc.)
- Each mode has its own keymap and syntax table
define_local_commandcreates mode-specific commands- Modes are automatically selected based on file_name_pattern or interpreter_name_pattern
Keymap (lib/textbringer/keymap.rb)
- Tree structure for key bindings (supports multi-stroke sequences)
- Uses
kbd()function to parse Emacs-style key notation - Key sequences can bind to commands (symbols) or nested keymaps
Commands (lib/textbringer/commands.rb and lib/textbringer/commands/*.rb)
- Commands are defined using
define_command(name, doc:) - Available as module functions in the Commands module
- Command groups: buffers, windows, files, isearch, replace, rectangle, etc.
- All commands accessible via Alt+x or key bindings
Plugins are loaded from ~/.textbringer/plugins/ via Plugin.load_plugins. Examples:
- Mournmail (mail client)
- MedicineShield (Mastodon client)
- textbringer-presentation
- textbringer-ghost_text
User configuration is loaded from:
~/.textbringer/init.rb(loaded first)~/.textbringer.rb(loaded after plugins)
Global configuration hash: CONFIG in lib/textbringer/config.rb
Key settings:
east_asian_ambiguous_width: Character width (1 or 2)tab_width,indent_tabs_mode: Indentationsyntax_highlight,highlight_buffer_size_limit: Syntax highlightingdefault_input_method: Input method for non-ASCII text
Support for non-ASCII input:
- T-Code (
lib/textbringer/input_methods/t_code_input_method.rb) - Hiragana (
lib/textbringer/input_methods/hiragana_input_method.rb) - Hangul (
lib/textbringer/input_methods/hangul_input_method.rb)
Tests use Test::Unit with custom Textbringer::TestCase base class in test/test_helper.rb.
Key test helpers:
FakeController: Test controller withtest_key_bufferfor simulating inputFakeCursesWindow: Mock curses window for headless testingpush_keys(keys): Simulate keyboard inputmkcdtmpdir: Create temporary directory for file testsWindow.setup_for_test: Initialize test environment
Tests are organized mirroring lib structure: test/textbringer/**/*.
define_command(:command_name, doc: "Description") do
# Command implementation
# Access current buffer: Buffer.current
# Get prefix arg: current_prefix_arg
endclass MyMode < Mode
define_local_command(:my_command) do
# Mode-specific implementation
end
endGLOBAL_MAP.define_key("\C-x\C-f", :find_file)
MODE_MAP.define_key("C-c C-c", :compile)- Always use
Buffer.currentto get the active buffer @buffer.pointis the cursor position@buffer.markfor region operations@buffer.insert(text),@buffer.delete_char(n)for modifications@buffer.save_pointand@buffer.goto_char(pos)for navigation
Window.currentis the active windowWindow.redisplayupdates the displayWindow.echo_areafor messagesmessage(text)to display in echo area
lib/textbringer.rb: Main entry point, requires all componentslib/textbringer/commands/*.rb: Command implementations by categorylib/textbringer/modes/*.rb: Major and minor modeslib/textbringer/faces/*.rb: Syntax highlighting face definitionsexe/txtb: Main executableexe/tbclient: Client for server modeexe/tbtags: Tag file generator
- The editor is designed to mimic Emacs conventions and terminology
- Key sequences use Emacs notation: C- (Control), M- (Meta/Alt), S- (Shift)
- The codebase uses extensive metaprogramming for command registration and mode definition
- All user-facing text editing operations should go through Buffer methods to maintain undo/redo support