github.com/jmigpin/editor@v1.6.0/core/terminalio.go (about)

     1  package core
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  
     9  	"github.com/jmigpin/editor/ui"
    10  	"github.com/jmigpin/editor/util/evreg"
    11  	"github.com/jmigpin/editor/util/iout"
    12  	"github.com/jmigpin/editor/util/uiutil/event"
    13  )
    14  
    15  //godebug:annotatefile
    16  
    17  type TerminalIO interface {
    18  	Init(tf *TerminalFilter)
    19  
    20  	Read([]byte) (int, error) // input interface
    21  	AddToRead([]byte)         // add input internally to be read
    22  
    23  	WriteOp(interface{}) error // accepted types: {[]byte,string}
    24  
    25  	Close() error
    26  }
    27  
    28  //----------
    29  //----------
    30  //----------
    31  
    32  type ERowTermIO struct {
    33  	erow *ERow
    34  	tf   *TerminalFilter
    35  
    36  	inputReg *evreg.Regist // input events
    37  
    38  	input struct {
    39  		sync.Mutex
    40  		cond    *sync.Cond
    41  		buf     bytes.Buffer
    42  		closing bool
    43  	}
    44  	update struct {
    45  		sync.Mutex
    46  		updating bool
    47  		ops      []interface{}
    48  	}
    49  }
    50  
    51  func NewERowTermIO(erow *ERow) *ERowTermIO {
    52  	tio := &ERowTermIO{erow: erow}
    53  	tio.input.cond = sync.NewCond(&tio.input)
    54  	return tio
    55  }
    56  
    57  func (tio *ERowTermIO) Init(tf *TerminalFilter) {
    58  	tio.tf = tf
    59  	tio.initInput()
    60  }
    61  
    62  func (tio *ERowTermIO) Close() error {
    63  	tio.inputReg.Unregister()
    64  
    65  	// signal to unblock waiting for a read
    66  	tio.input.Lock()
    67  	tio.input.closing = true
    68  	tio.input.cond.Signal()
    69  	tio.input.Unlock()
    70  
    71  	return nil
    72  }
    73  
    74  //----------
    75  
    76  func (tio *ERowTermIO) Read(b []byte) (int, error) {
    77  	tio.input.Lock()
    78  
    79  	for tio.input.buf.Len() == 0 && !tio.input.closing {
    80  		tio.input.cond.Wait()
    81  	}
    82  	defer tio.input.Unlock()
    83  	if tio.input.closing {
    84  		return 0, io.EOF
    85  	}
    86  	return tio.input.buf.Read(b)
    87  }
    88  
    89  func (tio *ERowTermIO) AddToRead(b []byte) {
    90  	tio.input.Lock()
    91  	defer tio.input.cond.Signal()
    92  	defer tio.input.Unlock()
    93  	tio.input.buf.Write(b)
    94  }
    95  
    96  //----------
    97  
    98  func (tio *ERowTermIO) WriteOp(op interface{}) error {
    99  	tio.updateWriteOp(op)
   100  	return nil
   101  }
   102  
   103  func (tio *ERowTermIO) updateWriteOp(op interface{}) {
   104  	tio.update.Lock()
   105  	defer tio.update.Unlock()
   106  
   107  	tio.appendOp(op)
   108  
   109  	if tio.update.updating {
   110  		return
   111  	}
   112  	tio.update.updating = true
   113  
   114  	tio.erow.Ed.UI.RunOnUIGoRoutine(func() {
   115  		tio.update.Lock()
   116  		defer tio.update.Unlock()
   117  		tio.update.updating = false
   118  		// clear ops at the end
   119  		defer func() { tio.update.ops = nil }()
   120  
   121  		for _, op := range tio.update.ops {
   122  			if err := tio.updateWriteOp2(op); err != nil {
   123  				tio.erow.Ed.Error(err)
   124  			}
   125  		}
   126  	})
   127  }
   128  
   129  func (tio *ERowTermIO) updateWriteOp2(op interface{}) error {
   130  	ta := tio.tf.erow.Row.TextArea
   131  	switch t := op.(type) {
   132  	case []byte:
   133  		if err := ta.AppendBytesClearHistory(t); err != nil {
   134  			return err
   135  		}
   136  	case string:
   137  		switch t {
   138  		case "clear":
   139  			if err := ta.SetBytesClearHistory(nil); err != nil {
   140  				return err
   141  			}
   142  		default:
   143  			panic(fmt.Sprintf("todo: %v", t))
   144  		}
   145  	default:
   146  		panic(fmt.Sprintf("todo: %v %T", t, t))
   147  	}
   148  	return nil
   149  }
   150  
   151  func (tio *ERowTermIO) appendOp(op interface{}) {
   152  	o := &tio.update.ops
   153  	switch t := op.(type) {
   154  	case []byte:
   155  		// copy to avoid losing/overwriting content
   156  		b := iout.CopyBytes(t)
   157  
   158  		// performance: append to previous op if possible
   159  		l := len(*o)
   160  		if l > 0 {
   161  			last := &(*o)[l-1]
   162  			if lb, ok := (*last).([]byte); ok {
   163  				*last = append(lb, b...)
   164  				return
   165  			}
   166  		}
   167  
   168  		*o = append(*o, b)
   169  	default:
   170  		*o = append(*o, op)
   171  	}
   172  }
   173  
   174  //----------
   175  
   176  func (tio *ERowTermIO) initInput() {
   177  	ta := tio.erow.Row.TextArea
   178  	tio.inputReg = ta.EvReg.Add(ui.TextAreaInputEventId, tio.onTextAreaInputEvent)
   179  }
   180  
   181  func (tio *ERowTermIO) onTextAreaInputEvent(ev0 interface{}) {
   182  	ev := ev0.(*ui.TextAreaInputEvent)
   183  	b, handled := tio.eventToBytes(ev.Event)
   184  	if len(b) > 0 {
   185  		tio.AddToRead(b)
   186  	}
   187  	ev.ReplyHandled = handled
   188  }
   189  
   190  //----------
   191  
   192  func (tio *ERowTermIO) eventToBytes(ev interface{}) ([]byte, event.Handled) {
   193  	// util funcs
   194  	keyboardEvs := func() bool {
   195  		return tio.erow.terminalOpt.keyEvents
   196  	}
   197  	byteOut := func(v byte, ru rune) []byte {
   198  		b := []byte{v}
   199  		// also add to output
   200  		if ru != 0 {
   201  			b2 := []byte(string(ru))
   202  			//_, _ = tio.tf.Write(b2) // send back to filter
   203  			_ = tio.WriteOp(b2) // add directly to output
   204  		}
   205  		return b
   206  	}
   207  
   208  	switch t := ev.(type) {
   209  	case *event.KeyDown:
   210  		if keyboardEvs() {
   211  			var b []byte
   212  			switch t.KeySym {
   213  			case event.KSymReturn:
   214  				b = byteOut('\n', '\n')
   215  			case event.KSymEscape:
   216  				b = []byte{27}
   217  			case event.KSymTab:
   218  				b = []byte{'\t'}
   219  			case event.KSymBackspace:
   220  				b = []byte{'\b'}
   221  			default:
   222  				b = []byte(string(t.Rune))
   223  			}
   224  			return b, true
   225  		}
   226  	}
   227  	return nil, false
   228  }