github.com/wtfutil/wtf@v0.43.0/modules/textfile/widget.go (about) 1 package textfile 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/alecthomas/chroma/formatters" 12 "github.com/alecthomas/chroma/lexers" 13 "github.com/alecthomas/chroma/styles" 14 "github.com/radovskyb/watcher" 15 "github.com/rivo/tview" 16 "github.com/wtfutil/wtf/utils" 17 "github.com/wtfutil/wtf/view" 18 ) 19 20 const ( 21 pollingIntervalms = 100 22 ) 23 24 type Widget struct { 25 view.MultiSourceWidget 26 view.TextWidget 27 28 settings *Settings 29 } 30 31 // NewWidget creates a new instance of a widget 32 func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget { 33 widget := Widget{ 34 MultiSourceWidget: view.NewMultiSourceWidget(settings.Common, "filePath", "filePaths"), 35 TextWidget: view.NewTextWidget(tviewApp, redrawChan, pages, settings.Common), 36 37 settings: settings, 38 } 39 40 // Don't use a timer for this widget, watch for filesystem changes instead 41 widget.settings.RefreshInterval = 0 42 43 widget.initializeKeyboardControls() 44 45 widget.SetDisplayFunction(widget.Refresh) 46 widget.View.SetWordWrap(true) 47 widget.View.SetWrap(settings.wrapText) 48 49 go widget.watchForFileChanges() 50 51 return &widget 52 } 53 54 /* -------------------- Exported Functions -------------------- */ 55 56 // Refresh is only called once on start-up. Its job is to display the 57 // text files that first time. After that, the watcher takes over 58 func (widget *Widget) Refresh() { 59 widget.Redraw(widget.content) 60 } 61 62 /* -------------------- Unexported Functions -------------------- */ 63 64 func (widget *Widget) content() (string, string, bool) { 65 title := fmt.Sprintf( 66 "[%s]%s[white]", 67 widget.settings.Colors.TextTheme.Title, 68 widget.CurrentSource(), 69 ) 70 71 _, _, width, _ := widget.View.GetRect() 72 text := widget.settings.PaginationMarker(len(widget.Sources), widget.Idx, width) + "\n" 73 74 if widget.settings.format { 75 text += widget.formattedText() 76 } else { 77 text += widget.plainText() 78 } 79 80 return title, text, widget.settings.wrapText 81 } 82 83 func (widget *Widget) formattedText() string { 84 filePath, _ := utils.ExpandHomeDir(widget.CurrentSource()) 85 86 file, err := os.Open(filepath.Clean(filePath)) 87 if err != nil { 88 return err.Error() 89 } 90 defer func() { _ = file.Close() }() 91 92 lexer := lexers.Match(filePath) 93 if lexer == nil { 94 lexer = lexers.Fallback 95 } 96 97 style := styles.Get(widget.settings.formatStyle) 98 if style == nil { 99 style = styles.Fallback 100 } 101 formatter := formatters.Get("terminal256") 102 if formatter == nil { 103 formatter = formatters.Fallback 104 } 105 106 contents, _ := io.ReadAll(file) 107 str := string(contents) 108 str = tview.Escape(str) 109 iterator, _ := lexer.Tokenise(nil, str) 110 111 var buf bytes.Buffer 112 err = formatter.Format(&buf, style, iterator) 113 if err != nil { 114 return err.Error() 115 } 116 117 return tview.TranslateANSI(buf.String()) 118 } 119 120 func (widget *Widget) plainText() string { 121 filePath, _ := utils.ExpandHomeDir(filepath.Clean(widget.CurrentSource())) 122 123 text, err := os.ReadFile(filepath.Clean(filePath)) 124 if err != nil { 125 return err.Error() 126 } 127 return tview.Escape(string(text)) 128 } 129 130 func (widget *Widget) watchForFileChanges() { 131 watch := watcher.New() 132 watch.FilterOps(watcher.Write) 133 134 go func() { 135 for { 136 select { 137 case <-watch.Event: 138 widget.Refresh() 139 case err := <-watch.Error: 140 fmt.Println(err) 141 os.Exit(1) 142 case <-watch.Closed: 143 return 144 case quit := <-widget.QuitChan(): 145 if quit { 146 return 147 } 148 } 149 } 150 }() 151 152 // Watch each textfile for changes 153 for _, source := range widget.Sources { 154 fullPath, err := utils.ExpandHomeDir(source) 155 if err == nil { 156 e := watch.Add(fullPath) 157 if e != nil { 158 fmt.Println(e) 159 os.Exit(1) 160 } 161 } 162 } 163 164 // Start the watching process - it'll check for changes every pollingIntervalms. 165 if err := watch.Start(time.Millisecond * pollingIntervalms); err != nil { 166 fmt.Println(err) 167 os.Exit(1) 168 } 169 }