@@ -9,8 +9,11 @@ import (
99 "slices"
1010 "strings"
1111 "sync"
12+ "unicode/utf8"
1213
14+ "github.com/kovidgoyal/kitty/tools/highlight"
1315 "github.com/kovidgoyal/kitty/tools/icons"
16+ "github.com/kovidgoyal/kitty/tools/tui/loop"
1417 "github.com/kovidgoyal/kitty/tools/utils"
1518 "github.com/kovidgoyal/kitty/tools/utils/humanize"
1619 "github.com/kovidgoyal/kitty/tools/utils/style"
@@ -30,12 +33,14 @@ type PreviewManager struct {
3033 WakeupMainThread func () bool
3134 cache map [string ]Preview
3235 lock sync.Mutex
36+ highlighter highlight.Highlighter
3337}
3438
35- func NewPreviewManager (err_chan chan error , settings Settings , WakeupMainThread func () bool ) * PreviewManager {
39+ func NewPreviewManager (err_chan chan error , settings Settings , WakeupMainThread func () bool ) (ans * PreviewManager ) {
40+ defer func () { sanitize = ans .highlighter .Sanitize }()
3641 return & PreviewManager {
3742 report_errors : err_chan , settings : settings , WakeupMainThread : WakeupMainThread ,
38- cache : make (map [string ]Preview ),
43+ cache : make (map [string ]Preview ), highlighter : highlight . NewHighlighter ( nil ),
3944 }
4045}
4146
@@ -104,6 +109,8 @@ func NewErrorPreview(err error) Preview {
104109 return & MessagePreview {msg : text }
105110}
106111
112+ var sanitize func (string ) string
113+
107114func write_file_metadata (abspath string , metadata fs.FileInfo , entries []fs.DirEntry ) (header string , trailers []string ) {
108115 buf := strings.Builder {}
109116 buf .Grow (4096 )
@@ -115,7 +122,7 @@ func write_file_metadata(abspath string, metadata fs.FileInfo, entries []fs.DirE
115122 add ("Size" , humanize .Bytes (uint64 (metadata .Size ())))
116123 case fs .ModeSymlink :
117124 if tgt , err := os .Readlink (abspath ); err == nil {
118- add ("Target" , tgt )
125+ add ("Target" , sanitize ( tgt ) )
119126 } else {
120127 add ("Target" , err .Error ())
121128 }
@@ -145,7 +152,7 @@ func write_file_metadata(abspath string, metadata fs.FileInfo, entries []fs.DirE
145152 slices .SortFunc (names , func (a , b string ) int { return strings .Compare (type_map [a ].lname , type_map [b ].lname ) })
146153 fmt .Fprintln (& buf , "Contents:" )
147154 for _ , n := range names {
148- trailers = append (trailers , icons .IconForFileWithMode (n , type_map [n ].ftype , false )+ " " + n )
155+ trailers = append (trailers , icons .IconForFileWithMode (n , type_map [n ].ftype , false )+ " " + sanitize ( n ) )
149156 }
150157 }
151158 return buf .String (), trailers
@@ -167,6 +174,96 @@ func NewFileMetadataPreview(abspath string, metadata fs.FileInfo) Preview {
167174 return & MessagePreview {title : title , msg : h , trailers : t }
168175}
169176
177+ type highlighed_data struct {
178+ text string
179+ light bool
180+ err error
181+ }
182+
183+ type TextFilePreview struct {
184+ plain_text , highlighted_text string
185+ highlighted_chan chan highlighed_data
186+ light bool
187+ }
188+
189+ func (p TextFilePreview ) IsValidForColorScheme (light bool ) bool { return p .light == light }
190+
191+ func (p TextFilePreview ) Render (h * Handler , x , y , width , height int ) {
192+ if p .highlighted_chan != nil {
193+ select {
194+ case hd := <- p .highlighted_chan :
195+ p .highlighted_chan = nil
196+ if hd .err == nil {
197+ p .highlighted_text = hd .text
198+ }
199+ default :
200+ }
201+ }
202+ text := p .highlighted_text
203+ if text == "" {
204+ text = p .plain_text
205+ }
206+ s := utils .NewLineScanner (text )
207+ buf := strings.Builder {}
208+ buf .Grow (1024 * height )
209+ for num := 0 ; s .Scan () && num < height ; num ++ {
210+ line := s .Text ()
211+ truncated := wcswidth .TruncateToVisualLength (line , width )
212+ buf .WriteString (fmt .Sprintf (loop .MoveCursorToTemplate , y + num , x ))
213+ buf .WriteString (truncated )
214+ if len (truncated ) < len (line ) {
215+ wcswidth .KeepOnlyCSI (line [len (truncated ):], & buf )
216+ }
217+ }
218+ buf .WriteString ("\x1b [m" ) // reset any highlight styles
219+ h .lp .QueueWriteString (buf .String ())
220+ }
221+
222+ func NewTextFilePreview (abspath string , metadata fs.FileInfo , highlighted_chan chan highlighed_data , sanitize func (string ) string ) Preview {
223+ data , err := os .ReadFile (abspath )
224+ if err != nil {
225+ return NewFileMetadataPreview (abspath , metadata )
226+ }
227+ text := utils .UnsafeBytesToString (data )
228+ if ! utf8 .ValidString (text ) {
229+ text = "Error: not valid utf-8 text"
230+ }
231+ return & TextFilePreview {plain_text : sanitize (text ), highlighted_chan : highlighted_chan , light : use_light_colors }
232+ }
233+
234+ type style_resolver struct {
235+ light bool
236+ light_style , dark_style string
237+ syntax_aliases map [string ]string
238+ }
239+
240+ func (s style_resolver ) StyleName () string {
241+ return utils .IfElse (s .light , s .light_style , s .dark_style )
242+ }
243+ func (s style_resolver ) UseLightColors () bool { return s .light }
244+ func (s style_resolver ) SyntaxAliases () map [string ]string { return s .syntax_aliases }
245+ func (s style_resolver ) TextForPath (path string ) (string , error ) {
246+ ans , err := os .ReadFile (path )
247+ if err == nil {
248+ return utils .UnsafeBytesToString (ans ), nil
249+ }
250+ return "" , err
251+ }
252+
253+ func (pm * PreviewManager ) highlight_file_async (path string , output chan highlighed_data ) {
254+ s := style_resolver {light : use_light_colors , syntax_aliases : pm .settings .SyntaxAliases ()}
255+ s .light_style , s .dark_style = pm .settings .HighlightStyles ()
256+ go func () {
257+ highlighted , err := pm .highlighter .HighlightFile (path , & s )
258+ if err != nil {
259+ debugprintln (fmt .Sprintf ("Failed to highlight: %s with error: %s" , path , err ))
260+ }
261+ output <- highlighed_data {text : highlighted , err : err , light : s .light }
262+ close (output )
263+ pm .WakeupMainThread ()
264+ }()
265+ }
266+
170267func (pm * PreviewManager ) invalidate_color_scheme_based_cached_items () {
171268 pm .lock .Lock ()
172269 defer pm .lock .Unlock ()
@@ -192,6 +289,13 @@ func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Pr
192289 }
193290 return NewDirectoryPreview (abspath , s )
194291 }
292+ mt := utils .GuessMimeType (filepath .Base (abspath ))
293+ const MAX_TEXT_FILE_SIZE = 16 * 1024 * 1024
294+ if s .Size () <= MAX_TEXT_FILE_SIZE && (utils .KnownTextualMimes [mt ] || strings .HasPrefix (mt , "text/" )) {
295+ ch := make (chan highlighed_data , 2 )
296+ pm .highlight_file_async (abspath , ch )
297+ return NewTextFilePreview (abspath , s , ch , pm .highlighter .Sanitize )
298+ }
195299 return NewFileMetadataPreview (abspath , s )
196300}
197301
0 commit comments