curses-syntax-highlighting 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- curses_syntax_highlighting-0.1.0/LICENSE +21 -0
- curses_syntax_highlighting-0.1.0/MANIFEST.in +2 -0
- curses_syntax_highlighting-0.1.0/PKG-INFO +155 -0
- curses_syntax_highlighting-0.1.0/README.md +100 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting/__init__.py +37 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting/display.py +341 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting/highlighter.py +64 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting/themes.py +155 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting/viewer.py +292 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting.egg-info/PKG-INFO +155 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting.egg-info/SOURCES.txt +23 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting.egg-info/dependency_links.txt +1 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting.egg-info/requires.txt +2 -0
- curses_syntax_highlighting-0.1.0/curses_syntax_highlighting.egg-info/top_level.txt +1 -0
- curses_syntax_highlighting-0.1.0/examples/1. Minimal.py +25 -0
- curses_syntax_highlighting-0.1.0/examples/2. Code Block Background Colour.py +73 -0
- curses_syntax_highlighting-0.1.0/examples/3. Scrolling Example.py +170 -0
- curses_syntax_highlighting-0.1.0/examples/4. Windowed Scrolling Example.py +207 -0
- curses_syntax_highlighting-0.1.0/examples/5. Advanced Example.py +138 -0
- curses_syntax_highlighting-0.1.0/examples/6. Split Preview Example.py +175 -0
- curses_syntax_highlighting-0.1.0/examples/7. String Input Example.py +219 -0
- curses_syntax_highlighting-0.1.0/examples/test_file_for_features.py +57 -0
- curses_syntax_highlighting-0.1.0/pyproject.toml +43 -0
- curses_syntax_highlighting-0.1.0/setup.cfg +4 -0
- curses_syntax_highlighting-0.1.0/setup.py +37 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Grim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: curses-syntax-highlighting
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Syntax highlighting for Python curses applications using Pygments
|
|
5
|
+
Home-page: https://github.com/grimandgreedy/python_curses_syntax_highlighting
|
|
6
|
+
Author: Grim
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Grim
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/GITHUB_USERNAME/python_curses_syntax_highlighting
|
|
30
|
+
Project-URL: Repository, https://github.com/GITHUB_USERNAME/python_curses_syntax_highlighting
|
|
31
|
+
Project-URL: Issues, https://github.com/GITHUB_USERNAME/python_curses_syntax_highlighting/issues
|
|
32
|
+
Keywords: curses,syntax,highlighting,terminal,tui,pygments
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
36
|
+
Classifier: Topic :: Terminals
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
46
|
+
Classifier: Operating System :: OS Independent
|
|
47
|
+
Requires-Python: >=3.7
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
License-File: LICENSE
|
|
50
|
+
Requires-Dist: pygments>=2.0.0
|
|
51
|
+
Requires-Dist: wcwidth>=0.2.0
|
|
52
|
+
Dynamic: home-page
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
Dynamic: requires-python
|
|
55
|
+
|
|
56
|
+
# Python Curses Syntax Highlighting
|
|
57
|
+
|
|
58
|
+
A Python library for displaying syntax-highlighted code in terminal applications using Python's curses library and Pygments for lexical analysis.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **Syntax Highlighting**: Uses Pygments to support hundreds of programming languages
|
|
63
|
+
- **File and String Input**: Display a file by path or pass a string directly
|
|
64
|
+
- **Efficient Large File Handling**: Lazy loading with block-based caching
|
|
65
|
+
- **Theme**: Light/dark theme support
|
|
66
|
+
- **Optional Features**: Line numbers, indent guides, configurable display area, line wrapping or truncation
|
|
67
|
+
- **Character Width Aware**: Properly handles special characters using wcwidth
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install curses-syntax-highlighting
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Two Input Modes
|
|
76
|
+
|
|
77
|
+
| | Function | Input | Lexer detection |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| **File** | `preview_text(stdscr, filepath)` | Path to a file on disk | Automatic (from filename + content) |
|
|
80
|
+
| **String** | `preview_string(stdscr, text, language=)` | A string variable | You supply the language name |
|
|
81
|
+
|
|
82
|
+
Use `preview_text` when rendering files. Use `preview_string` when the code comes from a variable - fetched from an API, generated at runtime, stored in a database, etc.
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import curses
|
|
88
|
+
from curses_syntax_highlighting import preview_text, preview_string
|
|
89
|
+
|
|
90
|
+
def main(stdscr):
|
|
91
|
+
# From a file - lexer detected automatically
|
|
92
|
+
preview_text(stdscr, 'example.py', show_line_numbers=True, theme='dark')
|
|
93
|
+
stdscr.getch()
|
|
94
|
+
|
|
95
|
+
# From a string - language supplied explicitly
|
|
96
|
+
code = open('example.rs').read()
|
|
97
|
+
preview_string(stdscr, code, language='rust', show_line_numbers=True)
|
|
98
|
+
stdscr.getch()
|
|
99
|
+
|
|
100
|
+
curses.wrapper(main)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Both functions accept the same options: `code_x`, `code_y`, `code_w`, `code_h`, `show_line_numbers`, `indent_guides`, `theme` (`'dark'`/`'light'`), `wrap`, `bg_color`, `scroll_offset`. They return a viewer with a `total_lines` attribute useful for implementing scroll bounds.
|
|
104
|
+
|
|
105
|
+
## Pure Renderer - No Key Stealing
|
|
106
|
+
|
|
107
|
+
The library never calls `getch()`. Every function is a draw call that renders and returns immediately, leaving all input handling to your application:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
while True:
|
|
111
|
+
preview_text(stdscr, filepath, scroll_offset=scroll)
|
|
112
|
+
key = stdscr.getch() # your app owns all input
|
|
113
|
+
if key == curses.KEY_DOWN:
|
|
114
|
+
scroll += 1
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## API Reference
|
|
118
|
+
|
|
119
|
+
### High-Level Functions
|
|
120
|
+
|
|
121
|
+
#### `preview_text(stdscr, filepath, **options) → LazyFileViewer`
|
|
122
|
+
|
|
123
|
+
Display a syntax-highlighted **file**. Lexer is detected automatically. Loads lazily in blocks - suitable for large files. Returns `None` if the file is not found.
|
|
124
|
+
|
|
125
|
+
#### `preview_string(stdscr, text, language=None, **options) → TextViewer`
|
|
126
|
+
|
|
127
|
+
Display a syntax-highlighted **string**. `language` is a Pygments alias (`'python'`, `'rust'`, `'javascript'`, …); pass `None` for plain text. The string is tokenized upfront and held in memory.
|
|
128
|
+
|
|
129
|
+
#### `display_code(stdscr, viewer, token_to_color, code_x, code_y, code_w, code_h, show_line_numbers, indent_guides, scroll_offset=0, wrap=False)`
|
|
130
|
+
|
|
131
|
+
Low-level renderer. Accepts any viewer with a `get_lines(start, count)` method and a `total_lines` attribute.
|
|
132
|
+
|
|
133
|
+
#### `init_colors(theme_name='dark', start_color_id=200, bg_color=None)`
|
|
134
|
+
|
|
135
|
+
Initialize curses color pairs for a theme. Must be called after `curses.initscr()`. Color pair IDs start at 200 by default, leaving 1–199 free for your application.
|
|
136
|
+
|
|
137
|
+
### Classes
|
|
138
|
+
|
|
139
|
+
#### `LazyFileViewer(filepath, lexer, block_size=50)` · `TextViewer(text, lexer)`
|
|
140
|
+
|
|
141
|
+
Both expose `get_lines(start, count)` and `total_lines`. `LazyFileViewer` reads the file in blocks on demand; `TextViewer` tokenizes the full string upfront. Pass either to `display_code()`.
|
|
142
|
+
|
|
143
|
+
#### `get_lexer(filepath)` · `get_lexer_for_language(language)`
|
|
144
|
+
|
|
145
|
+
Detect a lexer from a file path or a language alias respectively.
|
|
146
|
+
|
|
147
|
+
## Themes
|
|
148
|
+
|
|
149
|
+
Two built-in themes: `'dark'` and `'light'`. Both default to the terminal's background color. Customize by editing `THEMES` in `curses_syntax_highlighting/themes.py`.
|
|
150
|
+
|
|
151
|
+
## Requirements
|
|
152
|
+
|
|
153
|
+
- Python 3.7+
|
|
154
|
+
- pygments >= 2.0.0
|
|
155
|
+
- wcwidth >= 0.2.0
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Python Curses Syntax Highlighting
|
|
2
|
+
|
|
3
|
+
A Python library for displaying syntax-highlighted code in terminal applications using Python's curses library and Pygments for lexical analysis.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Syntax Highlighting**: Uses Pygments to support hundreds of programming languages
|
|
8
|
+
- **File and String Input**: Display a file by path or pass a string directly
|
|
9
|
+
- **Efficient Large File Handling**: Lazy loading with block-based caching
|
|
10
|
+
- **Theme**: Light/dark theme support
|
|
11
|
+
- **Optional Features**: Line numbers, indent guides, configurable display area, line wrapping or truncation
|
|
12
|
+
- **Character Width Aware**: Properly handles special characters using wcwidth
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install curses-syntax-highlighting
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Two Input Modes
|
|
21
|
+
|
|
22
|
+
| | Function | Input | Lexer detection |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| **File** | `preview_text(stdscr, filepath)` | Path to a file on disk | Automatic (from filename + content) |
|
|
25
|
+
| **String** | `preview_string(stdscr, text, language=)` | A string variable | You supply the language name |
|
|
26
|
+
|
|
27
|
+
Use `preview_text` when rendering files. Use `preview_string` when the code comes from a variable - fetched from an API, generated at runtime, stored in a database, etc.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import curses
|
|
33
|
+
from curses_syntax_highlighting import preview_text, preview_string
|
|
34
|
+
|
|
35
|
+
def main(stdscr):
|
|
36
|
+
# From a file - lexer detected automatically
|
|
37
|
+
preview_text(stdscr, 'example.py', show_line_numbers=True, theme='dark')
|
|
38
|
+
stdscr.getch()
|
|
39
|
+
|
|
40
|
+
# From a string - language supplied explicitly
|
|
41
|
+
code = open('example.rs').read()
|
|
42
|
+
preview_string(stdscr, code, language='rust', show_line_numbers=True)
|
|
43
|
+
stdscr.getch()
|
|
44
|
+
|
|
45
|
+
curses.wrapper(main)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Both functions accept the same options: `code_x`, `code_y`, `code_w`, `code_h`, `show_line_numbers`, `indent_guides`, `theme` (`'dark'`/`'light'`), `wrap`, `bg_color`, `scroll_offset`. They return a viewer with a `total_lines` attribute useful for implementing scroll bounds.
|
|
49
|
+
|
|
50
|
+
## Pure Renderer - No Key Stealing
|
|
51
|
+
|
|
52
|
+
The library never calls `getch()`. Every function is a draw call that renders and returns immediately, leaving all input handling to your application:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
while True:
|
|
56
|
+
preview_text(stdscr, filepath, scroll_offset=scroll)
|
|
57
|
+
key = stdscr.getch() # your app owns all input
|
|
58
|
+
if key == curses.KEY_DOWN:
|
|
59
|
+
scroll += 1
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### High-Level Functions
|
|
65
|
+
|
|
66
|
+
#### `preview_text(stdscr, filepath, **options) → LazyFileViewer`
|
|
67
|
+
|
|
68
|
+
Display a syntax-highlighted **file**. Lexer is detected automatically. Loads lazily in blocks - suitable for large files. Returns `None` if the file is not found.
|
|
69
|
+
|
|
70
|
+
#### `preview_string(stdscr, text, language=None, **options) → TextViewer`
|
|
71
|
+
|
|
72
|
+
Display a syntax-highlighted **string**. `language` is a Pygments alias (`'python'`, `'rust'`, `'javascript'`, …); pass `None` for plain text. The string is tokenized upfront and held in memory.
|
|
73
|
+
|
|
74
|
+
#### `display_code(stdscr, viewer, token_to_color, code_x, code_y, code_w, code_h, show_line_numbers, indent_guides, scroll_offset=0, wrap=False)`
|
|
75
|
+
|
|
76
|
+
Low-level renderer. Accepts any viewer with a `get_lines(start, count)` method and a `total_lines` attribute.
|
|
77
|
+
|
|
78
|
+
#### `init_colors(theme_name='dark', start_color_id=200, bg_color=None)`
|
|
79
|
+
|
|
80
|
+
Initialize curses color pairs for a theme. Must be called after `curses.initscr()`. Color pair IDs start at 200 by default, leaving 1–199 free for your application.
|
|
81
|
+
|
|
82
|
+
### Classes
|
|
83
|
+
|
|
84
|
+
#### `LazyFileViewer(filepath, lexer, block_size=50)` · `TextViewer(text, lexer)`
|
|
85
|
+
|
|
86
|
+
Both expose `get_lines(start, count)` and `total_lines`. `LazyFileViewer` reads the file in blocks on demand; `TextViewer` tokenizes the full string upfront. Pass either to `display_code()`.
|
|
87
|
+
|
|
88
|
+
#### `get_lexer(filepath)` · `get_lexer_for_language(language)`
|
|
89
|
+
|
|
90
|
+
Detect a lexer from a file path or a language alias respectively.
|
|
91
|
+
|
|
92
|
+
## Themes
|
|
93
|
+
|
|
94
|
+
Two built-in themes: `'dark'` and `'light'`. Both default to the terminal's background color. Customize by editing `THEMES` in `curses_syntax_highlighting/themes.py`.
|
|
95
|
+
|
|
96
|
+
## Requirements
|
|
97
|
+
|
|
98
|
+
- Python 3.7+
|
|
99
|
+
- pygments >= 2.0.0
|
|
100
|
+
- wcwidth >= 0.2.0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Python Curses Syntax Highlighting
|
|
2
|
+
|
|
3
|
+
A library for displaying syntax-highlighted code in terminal applications
|
|
4
|
+
using Python's curses library and Pygments for lexing.
|
|
5
|
+
|
|
6
|
+
Basic usage:
|
|
7
|
+
import curses
|
|
8
|
+
from curses_syntax_highlighting import preview_text
|
|
9
|
+
|
|
10
|
+
def main(stdscr):
|
|
11
|
+
preview_text(stdscr, 'myfile.py', show_line_numbers=True)
|
|
12
|
+
|
|
13
|
+
curses.wrapper(main)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = '0.1.0'
|
|
17
|
+
__author__ = 'Grim'
|
|
18
|
+
|
|
19
|
+
# Public API
|
|
20
|
+
from .themes import THEMES, get_theme
|
|
21
|
+
from .highlighter import init_colors, get_color_for_token
|
|
22
|
+
from .viewer import LazyFileViewer, TextViewer, get_lexer, get_lexer_for_language
|
|
23
|
+
from .display import display_code, preview_text, preview_string
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'THEMES',
|
|
27
|
+
'get_theme',
|
|
28
|
+
'init_colors',
|
|
29
|
+
'get_color_for_token',
|
|
30
|
+
'LazyFileViewer',
|
|
31
|
+
'TextViewer',
|
|
32
|
+
'get_lexer',
|
|
33
|
+
'get_lexer_for_language',
|
|
34
|
+
'display_code',
|
|
35
|
+
'preview_text',
|
|
36
|
+
'preview_string',
|
|
37
|
+
]
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""Display functions for rendering syntax-highlighted code in curses.
|
|
2
|
+
|
|
3
|
+
This module provides high-level functions for displaying syntax-highlighted
|
|
4
|
+
code in a curses window, with optional features like line numbers and indent guides.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import curses
|
|
8
|
+
import os
|
|
9
|
+
from wcwidth import wcwidth
|
|
10
|
+
|
|
11
|
+
from .highlighter import init_colors, get_color_for_token
|
|
12
|
+
from .viewer import LazyFileViewer, TextViewer, get_lexer, get_lexer_for_language
|
|
13
|
+
from pygments.token import Comment, Literal
|
|
14
|
+
|
|
15
|
+
_BRACKETS = frozenset('()[]{}')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_char_color(ch, ttype, token_to_color):
|
|
19
|
+
if ch in _BRACKETS and not (ttype is Comment or ttype in Comment or ttype in Literal.String):
|
|
20
|
+
bracket_color = token_to_color.get("bracket")
|
|
21
|
+
if bracket_color is not None:
|
|
22
|
+
return bracket_color
|
|
23
|
+
return get_color_for_token(ttype, token_to_color)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def display_code(
|
|
27
|
+
stdscr,
|
|
28
|
+
viewer,
|
|
29
|
+
token_to_color,
|
|
30
|
+
code_x,
|
|
31
|
+
code_y,
|
|
32
|
+
code_w,
|
|
33
|
+
code_h,
|
|
34
|
+
show_line_numbers,
|
|
35
|
+
indent_guides,
|
|
36
|
+
scroll_offset=0,
|
|
37
|
+
wrap=False
|
|
38
|
+
):
|
|
39
|
+
"""Display syntax-highlighted code in a curses window.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
stdscr: Curses window object
|
|
43
|
+
viewer: LazyFileViewer instance
|
|
44
|
+
token_to_color: Token-to-color mapping from init_colors()
|
|
45
|
+
code_x: X coordinate of display area
|
|
46
|
+
code_y: Y coordinate of display area
|
|
47
|
+
code_w: Width of display area
|
|
48
|
+
code_h: Height of display area
|
|
49
|
+
show_line_numbers: Whether to show line numbers
|
|
50
|
+
indent_guides: Whether to show indent guides
|
|
51
|
+
scroll_offset: Line number to start displaying from (default: 0)
|
|
52
|
+
wrap: Whether to wrap long lines (default: False, truncate instead)
|
|
53
|
+
"""
|
|
54
|
+
curses.curs_set(0)
|
|
55
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
56
|
+
|
|
57
|
+
# Get background color for clearing display area
|
|
58
|
+
bg_color_attr = token_to_color.get("background", curses.color_pair(0))
|
|
59
|
+
|
|
60
|
+
# Clear the display area with the background color
|
|
61
|
+
for i in range(code_h):
|
|
62
|
+
try:
|
|
63
|
+
stdscr.addstr(code_y + i, code_x, ' ' * min(code_w, max_x - code_x), bg_color_attr)
|
|
64
|
+
except curses.error:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
# Calculate line number width
|
|
68
|
+
num_lines = viewer.total_lines
|
|
69
|
+
line_num_width = (len(str(num_lines)) + 2) if show_line_numbers else 0
|
|
70
|
+
|
|
71
|
+
# Calculate available width for code content
|
|
72
|
+
content_width = code_w - line_num_width
|
|
73
|
+
|
|
74
|
+
# Get visible lines
|
|
75
|
+
visible = viewer.get_lines(scroll_offset, code_h)
|
|
76
|
+
|
|
77
|
+
display_row = 0 # Track which display row we're on
|
|
78
|
+
|
|
79
|
+
for idx, line in enumerate(visible):
|
|
80
|
+
if display_row >= code_h:
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
y = code_y + display_row
|
|
84
|
+
if y >= max_y or y >= code_y + code_h:
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
x = code_x
|
|
88
|
+
|
|
89
|
+
# Draw line numbers (only on first row of wrapped line)
|
|
90
|
+
if show_line_numbers:
|
|
91
|
+
ln = str(scroll_offset + idx + 1).rjust(line_num_width - 1) + " "
|
|
92
|
+
ln_color = token_to_color.get("line_number", curses.color_pair(0))
|
|
93
|
+
try:
|
|
94
|
+
stdscr.addstr(y, x, ln[:min(line_num_width, max_x - x)], ln_color)
|
|
95
|
+
except curses.error:
|
|
96
|
+
pass
|
|
97
|
+
x += line_num_width
|
|
98
|
+
|
|
99
|
+
# Draw code with syntax highlighting
|
|
100
|
+
if wrap:
|
|
101
|
+
# Wrapping mode: continue on next lines if content exceeds width
|
|
102
|
+
col = 0
|
|
103
|
+
char_idx = 0
|
|
104
|
+
in_leading_whitespace = True # Track if we're still in leading whitespace
|
|
105
|
+
|
|
106
|
+
while char_idx < len(line):
|
|
107
|
+
if display_row >= code_h:
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
y = code_y + display_row
|
|
111
|
+
if y >= max_y or y >= code_y + code_h:
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
# For continuation lines, fill line number area with spaces
|
|
115
|
+
if col >= content_width and char_idx < len(line):
|
|
116
|
+
display_row += 1
|
|
117
|
+
col = 0
|
|
118
|
+
in_leading_whitespace = False # Wrapped lines are not leading whitespace
|
|
119
|
+
if display_row >= code_h:
|
|
120
|
+
break
|
|
121
|
+
y = code_y + display_row
|
|
122
|
+
if y >= max_y or y >= code_y + code_h:
|
|
123
|
+
break
|
|
124
|
+
x = code_x
|
|
125
|
+
|
|
126
|
+
# Draw empty line number area for wrapped continuation
|
|
127
|
+
if show_line_numbers:
|
|
128
|
+
try:
|
|
129
|
+
stdscr.addstr(y, x, " " * line_num_width, bg_color_attr)
|
|
130
|
+
except curses.error:
|
|
131
|
+
pass
|
|
132
|
+
x += line_num_width
|
|
133
|
+
|
|
134
|
+
if char_idx >= len(line):
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
ch, ttype = line[char_idx]
|
|
138
|
+
width = wcwidth(ch)
|
|
139
|
+
if width < 0:
|
|
140
|
+
width = 0 # unprintable
|
|
141
|
+
|
|
142
|
+
# Check if character fits in current line
|
|
143
|
+
if col + width > content_width:
|
|
144
|
+
# Move to next line
|
|
145
|
+
display_row += 1
|
|
146
|
+
col = 0
|
|
147
|
+
in_leading_whitespace = False # Wrapped content is not leading whitespace
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Track leading whitespace - once we see a non-space, we're done
|
|
151
|
+
if ch != ' ' and ch != '\t':
|
|
152
|
+
in_leading_whitespace = False
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
# Only draw indent guides in leading whitespace
|
|
156
|
+
if indent_guides and in_leading_whitespace and ch == ' ' and (col % 4 == 0) and col + width <= content_width:
|
|
157
|
+
guide = token_to_color.get("indent_guide", curses.color_pair(0))
|
|
158
|
+
stdscr.addstr(y, x + col, '│', guide)
|
|
159
|
+
else:
|
|
160
|
+
color = _get_char_color(ch, ttype, token_to_color)
|
|
161
|
+
stdscr.addstr(y, x + col, ch, color)
|
|
162
|
+
except curses.error:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
col += width
|
|
166
|
+
char_idx += 1
|
|
167
|
+
|
|
168
|
+
display_row += 1
|
|
169
|
+
|
|
170
|
+
else:
|
|
171
|
+
# Truncate mode: stop at width boundary
|
|
172
|
+
col = 0
|
|
173
|
+
in_leading_whitespace = True # Track if we're still in leading whitespace
|
|
174
|
+
|
|
175
|
+
for ch, ttype in line:
|
|
176
|
+
width = wcwidth(ch)
|
|
177
|
+
if width < 0:
|
|
178
|
+
width = 0 # unprintable
|
|
179
|
+
|
|
180
|
+
# Check boundary: respect code_w, not max_x
|
|
181
|
+
if col + width > content_width:
|
|
182
|
+
break
|
|
183
|
+
|
|
184
|
+
# Track leading whitespace - once we see a non-space, we're done
|
|
185
|
+
if ch != ' ' and ch != '\t':
|
|
186
|
+
in_leading_whitespace = False
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
# Only draw indent guides in leading whitespace
|
|
190
|
+
if indent_guides and in_leading_whitespace and ch == ' ' and (col % 4 == 0):
|
|
191
|
+
guide = token_to_color.get("indent_guide", curses.color_pair(0))
|
|
192
|
+
stdscr.addstr(y, x + col, '│', guide)
|
|
193
|
+
else:
|
|
194
|
+
color = _get_char_color(ch, ttype, token_to_color)
|
|
195
|
+
stdscr.addstr(y, x + col, ch, color)
|
|
196
|
+
except curses.error:
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
col += width
|
|
200
|
+
|
|
201
|
+
display_row += 1
|
|
202
|
+
|
|
203
|
+
stdscr.refresh()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def preview_text(
|
|
207
|
+
stdscr,
|
|
208
|
+
filepath,
|
|
209
|
+
code_x=0,
|
|
210
|
+
code_y=0,
|
|
211
|
+
code_w=None,
|
|
212
|
+
code_h=None,
|
|
213
|
+
show_line_numbers=False,
|
|
214
|
+
indent_guides=False,
|
|
215
|
+
theme="dark",
|
|
216
|
+
wrap=False,
|
|
217
|
+
bg_color=None,
|
|
218
|
+
scroll_offset=0
|
|
219
|
+
):
|
|
220
|
+
"""Display a syntax-highlighted preview of a text file.
|
|
221
|
+
|
|
222
|
+
This is the main high-level function for displaying a file with
|
|
223
|
+
syntax highlighting.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
stdscr: Curses window object
|
|
227
|
+
filepath: Path to the file to preview
|
|
228
|
+
code_x: X coordinate of display area (default: 0)
|
|
229
|
+
code_y: Y coordinate of display area (default: 0)
|
|
230
|
+
code_w: Width of display area (default: remaining width)
|
|
231
|
+
code_h: Height of display area (default: remaining height)
|
|
232
|
+
show_line_numbers: Whether to show line numbers (default: False)
|
|
233
|
+
indent_guides: Whether to show indent guides (default: False)
|
|
234
|
+
theme: Color theme to use ('dark' or 'light', default: 'dark')
|
|
235
|
+
wrap: Whether to wrap long lines (default: False, truncate instead)
|
|
236
|
+
bg_color: Optional background color override. Use -1 for terminal
|
|
237
|
+
default (recommended), or a curses color number (0-255).
|
|
238
|
+
If None, uses theme default (-1 for terminal background).
|
|
239
|
+
scroll_offset: Line number to start displaying from (default: 0)
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
LazyFileViewer: The viewer instance (access viewer.total_lines for line count)
|
|
243
|
+
Returns None on error (file not found, etc.)
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
if not os.path.isfile(filepath):
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
lexer = get_lexer(filepath)
|
|
250
|
+
|
|
251
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
252
|
+
|
|
253
|
+
if code_w is None:
|
|
254
|
+
code_w = max_x - code_x
|
|
255
|
+
if code_h is None:
|
|
256
|
+
code_h = max_y - code_y
|
|
257
|
+
|
|
258
|
+
viewer = LazyFileViewer(filepath, lexer, block_size=code_h)
|
|
259
|
+
token_to_color = init_colors(theme, bg_color=bg_color)
|
|
260
|
+
|
|
261
|
+
display_code(
|
|
262
|
+
stdscr=stdscr,
|
|
263
|
+
viewer=viewer,
|
|
264
|
+
token_to_color=token_to_color,
|
|
265
|
+
code_x=code_x,
|
|
266
|
+
code_y=code_y,
|
|
267
|
+
code_w=code_w,
|
|
268
|
+
code_h=code_h,
|
|
269
|
+
show_line_numbers=show_line_numbers,
|
|
270
|
+
indent_guides=indent_guides,
|
|
271
|
+
scroll_offset=scroll_offset,
|
|
272
|
+
wrap=wrap,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return viewer
|
|
276
|
+
except Exception:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def preview_string(
|
|
281
|
+
stdscr,
|
|
282
|
+
text,
|
|
283
|
+
language=None,
|
|
284
|
+
code_x=0,
|
|
285
|
+
code_y=0,
|
|
286
|
+
code_w=None,
|
|
287
|
+
code_h=None,
|
|
288
|
+
show_line_numbers=False,
|
|
289
|
+
indent_guides=False,
|
|
290
|
+
theme="dark",
|
|
291
|
+
wrap=False,
|
|
292
|
+
bg_color=None,
|
|
293
|
+
scroll_offset=0,
|
|
294
|
+
):
|
|
295
|
+
"""Display a syntax-highlighted preview of a plain-text string.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
stdscr: Curses window object.
|
|
299
|
+
text: The source text to display.
|
|
300
|
+
language: Pygments language alias for syntax highlighting, e.g. 'python',
|
|
301
|
+
'rust', 'javascript'. Pass None for plain text with no highlighting.
|
|
302
|
+
code_x: X coordinate of display area (default: 0).
|
|
303
|
+
code_y: Y coordinate of display area (default: 0).
|
|
304
|
+
code_w: Width of display area (default: remaining width).
|
|
305
|
+
code_h: Height of display area (default: remaining height).
|
|
306
|
+
show_line_numbers: Whether to show line numbers (default: False).
|
|
307
|
+
indent_guides: Whether to show indent guides (default: False).
|
|
308
|
+
theme: Color theme ('dark' or 'light', default: 'dark').
|
|
309
|
+
wrap: Whether to wrap long lines (default: False).
|
|
310
|
+
bg_color: Background color override. Use -1 or None for terminal default.
|
|
311
|
+
scroll_offset: Line number to start displaying from (default: 0).
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
TextViewer: The viewer instance (access viewer.total_lines for line count).
|
|
315
|
+
"""
|
|
316
|
+
lexer = get_lexer_for_language(language)
|
|
317
|
+
|
|
318
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
319
|
+
if code_w is None:
|
|
320
|
+
code_w = max_x - code_x
|
|
321
|
+
if code_h is None:
|
|
322
|
+
code_h = max_y - code_y
|
|
323
|
+
|
|
324
|
+
viewer = TextViewer(text, lexer)
|
|
325
|
+
token_to_color = init_colors(theme, bg_color=bg_color)
|
|
326
|
+
|
|
327
|
+
display_code(
|
|
328
|
+
stdscr=stdscr,
|
|
329
|
+
viewer=viewer,
|
|
330
|
+
token_to_color=token_to_color,
|
|
331
|
+
code_x=code_x,
|
|
332
|
+
code_y=code_y,
|
|
333
|
+
code_w=code_w,
|
|
334
|
+
code_h=code_h,
|
|
335
|
+
show_line_numbers=show_line_numbers,
|
|
336
|
+
indent_guides=indent_guides,
|
|
337
|
+
scroll_offset=scroll_offset,
|
|
338
|
+
wrap=wrap,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
return viewer
|