github.com/grzegorz-zur/bm@v0.0.0-20240312214136-6fc133e3e2c0/editor.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "github.com/gdamore/tcell" 6 "log" 7 "os" 8 "os/signal" 9 "syscall" 10 "time" 11 ) 12 13 const ( 14 // TickInterval designates time between file modification checks. 15 TickInterval = 200 * time.Millisecond 16 ) 17 18 // Editor represents the editor. 19 type Editor struct { 20 Modes 21 Files 22 screenCreate ScreenCreate 23 screen tcell.Screen 24 view *View 25 content string 26 visible bool 27 events chan tcell.Event 28 deaf chan struct{} 29 check chan struct{} 30 pause chan struct{} 31 unpause chan struct{} 32 quit chan struct{} 33 done chan struct{} 34 } 35 36 // ScreenCreate creates a new screen. 37 type ScreenCreate func() (tcell.Screen, error) 38 39 // New creates new editor. 40 func New(screenCreate ScreenCreate, paths []string) *Editor { 41 editor := &Editor{ 42 screenCreate: screenCreate, 43 view: NewView(Size{}), 44 events: make(chan tcell.Event), 45 deaf: make(chan struct{}), 46 check: make(chan struct{}), 47 pause: make(chan struct{}, 1), 48 unpause: make(chan struct{}), 49 quit: make(chan struct{}, 1), 50 done: make(chan struct{}), 51 } 52 editor.Modes.Command = &Command{editor: editor} 53 editor.Modes.Input = &Input{editor: editor} 54 editor.Modes.Select = &Select{editor: editor} 55 editor.Modes.Switch = &Switch{editor: editor} 56 for _, path := range paths { 57 editor.Open(path) 58 } 59 editor.SwitchFile(Forward) 60 editor.SwitchMode(editor.Command) 61 return editor 62 } 63 64 // Start starts the editor. 65 func (editor *Editor) Start() (err error) { 66 editor.screen, err = editor.screenCreate() 67 if err != nil { 68 return fmt.Errorf("error creating screen: %w", err) 69 } 70 err = editor.screen.Init() 71 if err != nil { 72 return fmt.Errorf("error initializing screen: %w", err) 73 } 74 go editor.signals() 75 go editor.listen() 76 go editor.tick() 77 go editor.run() 78 return nil 79 } 80 81 // Check signals file modification check. 82 func (editor *Editor) Check() { 83 editor.check <- struct{}{} 84 } 85 86 // Pause pauses the editor. 87 func (editor *Editor) Pause() { 88 editor.pause <- struct{}{} 89 } 90 91 // Quit quits the editor. 92 func (editor *Editor) Quit() { 93 editor.quit <- struct{}{} 94 } 95 96 // Wait waits for the editor to finish. 97 func (editor *Editor) Wait() { 98 <-editor.done 99 } 100 101 func (editor *Editor) signals() { 102 signals := make(chan os.Signal, 1) 103 signal.Notify(signals, syscall.SIGCONT, syscall.SIGTERM) 104 for signal := range signals { 105 switch signal { 106 case syscall.SIGCONT: 107 editor.unpause <- struct{}{} 108 case syscall.SIGTERM: 109 editor.quit <- struct{}{} 110 } 111 } 112 } 113 114 // ToggleVisible changes visibility. 115 func (editor *Editor) ToggleVisible() { 116 editor.visible = !editor.visible 117 } 118 119 // Copy copies selection to buffer. 120 func (editor *Editor) Copy() { 121 editor.content = editor.File.Copy() 122 editor.SwitchMode(editor.Command) 123 } 124 125 // Cut cuts selection to buffer. 126 func (editor *Editor) Cut() { 127 editor.content = editor.File.Cut() 128 editor.SwitchMode(editor.Command) 129 } 130 131 // Paste pastes buffer. 132 func (editor *Editor) Paste() { 133 editor.Insert(editor.content) 134 } 135 136 // LineAbove starts line above current line. 137 func (editor *Editor) LineAbove() { 138 editor.MoveLineStart() 139 editor.Insert(string(EOL)) 140 editor.MoveUp() 141 editor.SwitchMode(editor.Input) 142 } 143 144 // LineBelow starts line below current line. 145 func (editor *Editor) LineBelow() { 146 editor.MoveLineEnd() 147 editor.Insert(string(EOL)) 148 editor.SwitchMode(editor.Input) 149 } 150 151 func (editor *Editor) listen() { 152 for { 153 event := editor.screen.PollEvent() 154 if event != nil { 155 editor.events <- event 156 } else { 157 break 158 } 159 } 160 editor.deaf <- struct{}{} 161 } 162 163 func (editor *Editor) tick() { 164 for { 165 time.Sleep(TickInterval) 166 editor.check <- struct{}{} 167 } 168 } 169 170 func (editor *Editor) run() { 171 defer close(editor.done) 172 render := true 173 for { 174 if render && editor.screen != nil { 175 err := editor.render() 176 editor.report(err) 177 } 178 select { 179 case event := <-editor.events: 180 err := editor.handle(event) 181 editor.report(err) 182 render = true 183 case <-editor.check: 184 if editor.Empty() { 185 render = false 186 } else { 187 read, err := editor.Read(false) 188 render = read 189 editor.report(err) 190 } 191 case <-editor.pause: 192 err := editor.background() 193 editor.report(err) 194 case <-editor.unpause: 195 err := editor.foreground() 196 editor.report(err) 197 case <-editor.quit: 198 editor.close() 199 return 200 } 201 } 202 } 203 204 func (editor *Editor) handle(event tcell.Event) error { 205 switch tevent := event.(type) { 206 case *tcell.EventKey: 207 if tevent.Key() == tcell.KeyRune { 208 return editor.Rune(tevent.Rune()) 209 } 210 key, ok := keymap[tevent.Key()] 211 if ok { 212 return editor.Key(key) 213 } 214 case *tcell.EventResize: 215 width, height := tevent.Size() 216 if height > 0 { 217 height-- 218 } 219 size := Size{height, width} 220 editor.view = NewView(size) 221 } 222 return nil 223 } 224 225 func (editor *Editor) report(err error) { 226 if err != nil { 227 log.Println(err) 228 } 229 } 230 231 func (editor *Editor) background() error { 232 editor.screen.Fini() 233 <-editor.deaf 234 pid := os.Getpid() 235 process, err := os.FindProcess(pid) 236 if err != nil { 237 return fmt.Errorf("error pausing process %+v: %w", process, err) 238 } 239 err = process.Signal(syscall.SIGSTOP) 240 if err != nil { 241 return fmt.Errorf("error signalling process %+v: %w", process, err) 242 } 243 return nil 244 } 245 246 func (editor *Editor) foreground() (err error) { 247 editor.screen, err = editor.screenCreate() 248 if err != nil { 249 return fmt.Errorf("error creating screen: %w", err) 250 } 251 err = editor.screen.Init() 252 if err != nil { 253 return fmt.Errorf("error initializing screen: %w", err) 254 } 255 go editor.listen() 256 return nil 257 } 258 259 func (editor *Editor) close() { 260 editor.screen.Fini() 261 <-editor.deaf 262 } 263 264 func (editor *Editor) render() error { 265 editor.view.Clear() 266 editor.view.Visible = editor.visible 267 err := editor.Mode.Render(editor.view) 268 if err != nil { 269 return fmt.Errorf("error on rendering: %w", err) 270 } 271 size := editor.view.Size 272 for line := 0; line < size.Lines; line++ { 273 for column := 0; column < size.Columns; column++ { 274 rune := editor.view.Content[line][column] 275 selection := editor.view.Selection[line][column] 276 style := tcell.StyleDefault.Reverse(selection) 277 editor.screen.SetContent(column, line, rune, nil, style) 278 } 279 } 280 status := []rune(editor.view.Status) 281 prompt := []rune(editor.view.Prompt) 282 line := size.Lines 283 for column := 0; column < size.Columns; column++ { 284 rune := ' ' 285 style := tcell.StyleDefault.Background(colors[editor.view.Color]) 286 if column < len(status) { 287 rune = status[column] 288 } 289 if column >= len(status)+1 && column < len(status)+len(prompt)+1 { 290 rune = prompt[column-len(status)-1] 291 style = style.Reverse(true) 292 } 293 editor.screen.SetContent(column, line, rune, nil, style) 294 } 295 switch editor.view.Cursor { 296 case CursorNone: 297 editor.screen.HideCursor() 298 case CursorContent: 299 editor.screen.ShowCursor(editor.view.Position.Column, editor.view.Position.Line) 300 case CursorPrompt: 301 editor.screen.ShowCursor(len(status)+1+len(prompt), line) 302 } 303 editor.screen.Show() 304 return nil 305 }