XeFM includes a comprehensive built-in text file viewer with syntax highlighting, search functionality, and remote file support. The viewer provides a clean, efficient way to view text files from both local and remote storage without leaving the file manager.
_EXT_LEXERS) for common source types↑↓ arrow keys←→ arrow keysPage Up/Down for faster scrollingHome/End keysn key (on by default)w key (off by default)s key (on by default if pygments available)f to search within the current file↑↓ to move between matchesHighlighting is driven by pygments, so any language pygments has a lexer for is supported. Lexer selection is:
pygments.lexers.get_lexer_for_filename(path.name) — pygments’ own filename
matching (handles Dockerfile, Makefile, .rst, and most extensions).ClassNotFound, a small extension fallback map, _EXT_LEXERS in
xefm/text_viewer.py (.py, .js, .ts, .json, .md, .yml/.yaml,
.xml, .html, .css, .sh/.bash, .c/.cpp/.h/.hpp, .java,
.go, .rs, .php, .rb, .sql, .ini/.cfg/.conf, .toml).TextLexer (plain, uncolored).Token categories are mapped to a small palette (DEFAULT_SYNTAX, VS Code Dark+)
that a theme may override via extras['syntax']. Structured formats such as
JSON and CSV also have dedicated rich renderers (see
JSON_CSV_VIEWERS_IMPLEMENTATION.md) reachable via the view-mode toggle.
v to explicitly open in text viewer| Key | Action |
|—–|——–|
| q or ESC | Exit viewer and return to XeFM |
| ↑↓ | Scroll up/down |
| ←→ | Scroll left/right |
| Page Up/Down | Page scrolling |
| Home/End | Jump to start/end of file |
| n | Toggle line numbers on/off |
| w | Toggle line wrapping on/off |
| s | Toggle syntax highlighting on/off |
| f or F | Enter search mode |
| Key | Action |
|—–|——–|
| f or F | Enter search mode |
| ESC or Enter | Exit search mode |
| Backspace | Remove last search character |
| ↑ or k | Previous match |
| ↓ or j | Next match |
| Type characters | Add to search pattern (incremental) |
The viewer interface provides comprehensive status information:
Header:
Status Bar (bottom):
The text viewer uses xefm.path’s abstraction layer to support both local and remote files:
Currently supports any storage backend implemented in the xefm.path system:
s3://bucket/key)PathImplFile: example.txtS3: example.txtSCHEME: example.txtSpecific exception handling for different error types:
FileNotFoundError for missing filesPermissionError for access deniedOSError for general I/O errorsUses file_path.read_text() with multiple encoding attempts:
If text reading fails, attempts file_path.read_bytes():
try:
content = file_path.read_text(encoding='utf-8')
except FileNotFoundError:
# File doesn't exist
except PermissionError:
# Access denied
except OSError as e:
# General I/O error (including network issues)
The text viewer uses a curses-native approach to syntax highlighting:
Multi-step approach to identify text files:
Content is drawn in a fixed-advance face (MONO), so a column is a character and
the gutter, horizontal scroll, and highlights all align by column. self.left
(and self.top) are floats, so a GUI pan is smooth rather than cell-snapped.
Tabs are expanded once at read time by the module function _expand_tabs(),
column-aware to _TAB (8) stops; _read_lines() runs it on every line, so the
rest of the viewer never sees a raw tab.
_draw_line(ctx, y, line_idx, col0) renders the visible column window
[col0, col0 + content_w). It walks the line’s (text, fg) segments tracking a
running character index col, clips each to vis_start = max(col, col0_int) /
vis_end = min(seg_end, window_end), and slices by index arithmetic. col0 may
be fractional: the row shifts left by its fractional part (xfrac) for smooth
pan, the gutter fill (drawn after) masks the left bleed, and the body’s clip
trims the partial right edge.
War story — don’t locate a character by value. An earlier version found the first visible character with
text.index(char).str.indexreturns the first occurrence of that character value, not the character’s position, so any line containing a repeated character (e.g.0123450123...) rendered from the wrong column at horizontal offsets past the repeat. The durable fix is to never search for a character by value — track the running character index explicitly (the current code’s accumulatingcol).
Mouse text selection + clipboard copy in the modal viewer. The feature spans two repos:
clipboard_rich capability
(Panel.set_clipboard_rich) and MarkdownView’s own selection + rich-HTML
copy (documented in puikit/docs/widget_catalog.md).MarkdownView in rich mode.Raw text mode. _RawTextSelection holds a (line, col) selection over the
source lines (monospace, so a column is a character), using PuiKit’s
MultiClickTracker + word_bounds for the word/line gestures. It is a local
counterpart to PuiKit’s SelectableText mixin, which can’t be reused because
this viewer scrolls vertically and horizontally and draws its own line-number
gutter. _pos_at(ex, ey) maps a layer-local point through _body_rect and the
current top/left scroll (unwrapping _row_map when wrapping) to a
(line, col); _draw_selection overlays the selected span of each visible row
over theme.text_selection_bg (mirroring the search-match overlay
_draw_matches). handle_event processes MOUSE_DOWN/UP/DRAG plus
Cmd/Ctrl+C (copy, plain text via Panel.set_clipboard) and
Cmd/Ctrl+A (select-all); a press outside the body clears the selection.
Rich mode. _forward_mouse_to_rich translates a mouse event into the
embedded MarkdownView’s coordinate space (event.translated(-bx0, -by0)) so
its own selection and link clicks work through this modal viewer. KEY events
(including Cmd+C) are already forwarded in rich mode, so the widget’s copy
path needs no extra wiring. xefm.viewer_registry._build_markdown builds the
file viewer’s MarkdownView with selectable=True; help / message-box
MarkdownViews build without the flag and stay inert.
Tests: test/test_viewer_selection.py (raw-mode drag / multi-line / select-all /
press-outside-clears, and rich-mode mouse + copy forwarding). User-facing
behavior: doc/TEXT_VIEWER_FEATURE.md.
The text viewer works with no external dependencies - it uses Python’s built-in libraries and the curses interface.
For full syntax highlighting support, install pygments:
pip install pygments
Without pygments: The viewer still works but displays files as plain text without syntax coloring.
With pygments: Syntax highlighting for any language pygments can lex, using the theme’s syntax palette.
Remote file support is provided through the xefm.path system:
boto3 library for AWS S3 accessThe viewer is a full-window modal PuiKit Widget, pushed over the active panel
with show_text_viewer:
from xefm.path import Path
from xefm.text_viewer import show_text_viewer
# Local file
show_text_viewer(panel, Path('/home/user/document.txt'), state_manager=state_manager)
# S3 file (same call — xefm.path.Path abstracts the backend)
show_text_viewer(panel, Path('s3://my-bucket/document.txt'))
There is no is_text_file() predicate to call ahead of time, and no list of
text extensions — the viewer decides from the file’s bytes as it reads it:
from xefm.text_viewer import looks_binary
looks_binary(path) # NUL byte in the first 1024 bytes
lines, is_error = _read_lines(path) # placeholder line when binary
_read_lines() sniffs with looks_binary() before attempting any decode,
then tries utf-8, latin-1, cp1252 for everything else.
The ordering is load-bearing, not stylistic.
latin-1maps all 256 byte values, so it never raisesUnicodeDecodeError— any decode loop containing it always succeeds. This code originally sniffed only after the loop, in an “if nothing decoded” branch that could therefore never run: the placeholder was unreachable and a PNG rendered as ~45,000 lines of mojibake. If you reorder this,test/test_binary_file_handling.pywill fail.
Content search needs a cheaper, standalone check and has its own:
XeFMApp._looks_textual(path) in xefm/app.py. It differs deliberately — an empty
file is “nothing to grep” (False) but is perfectly viewable (not binary).
The principle: detect capability from the bytes, configure preference by extension. Extension lists belong in FILE_ASSOCIATIONS (which application the user prefers), never in text detection — they get files with no extension, an unknown one, or a misleading one wrong, and sniffing gets all three right.
# test_syntax.py - automatically highlighted
def hello_world():
"""A simple function"""
message = "Hello, World!"
print(f"Message: {message}")
return True
{
"name": "XeFM Text Viewer",
"features": ["syntax highlighting", "line numbers", "remote support"],
"supported": true
}
# config.ini - automatically detected and highlighted
[section]
key = value
debug = true
File not found: /path/to/missing.txt
Permission denied: /path/to/restricted.txt
[Binary file - cannot display as text]
File not found: s3://bucket/missing.txt
Permission denied: s3://private-bucket/restricted.txt
Error reading file: Connection timeout
Network error: Unable to connect to S3
The text viewer respects XeFM’s configuration system:
~/.xefm/config.pytest/test_binary_file_handling.py — the binary-sniff ordering described in
Text File Detection above (a PNG must not render as mojibake).test/test_viewer_selection.py — raw-mode and rich-mode text selection / copy.Q: Syntax highlighting not working
A: Install pygments with pip install pygments
Q: File shows as binary when it should be text A: Check file encoding - viewer supports UTF-8, Latin-1, and CP1252
Q: Large files are slow to open A: This is expected - the viewer loads content progressively for better performance
Q: Colors look wrong in terminal A: Ensure your terminal supports colors and try different XeFM color schemes
Q: S3 files not accessible A: Ensure AWS credentials are configured and check network connectivity
Q: Permission denied on remote files A: Verify read access to remote resources and check authentication
Enable debug logging to see detailed error information:
import logging
logging.basicConfig(level=logging.DEBUG)
? key~/.xefm/config.pyThe XeFM Text Viewer System provides a powerful, integrated solution for viewing and examining text files from both local and remote storage without leaving your file management workflow. Whether you’re browsing code, checking configuration files, reading documentation, or accessing files from cloud storage, the viewer offers a smooth, efficient experience with professional syntax highlighting and comprehensive search capabilities.
The system’s architecture ensures consistent behavior across all storage types while maintaining high performance and reliability. The unified interface means users can work with local and remote files using the same familiar controls and features.