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  }