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

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/jmigpin/editor/core/toolbarparser"
    14  	"github.com/jmigpin/editor/ui"
    15  	"github.com/jmigpin/editor/util/iout"
    16  	"github.com/jmigpin/editor/util/iout/iorw"
    17  	"github.com/jmigpin/editor/util/uiutil/event"
    18  )
    19  
    20  //godebug:annotatefile
    21  
    22  //----------
    23  
    24  type ERow struct {
    25  	Ed     *Editor
    26  	Row    *ui.Row
    27  	Info   *ERowInfo
    28  	Exec   *ERowExec
    29  	TbData toolbarparser.Data
    30  
    31  	highlightDuplicates bool
    32  
    33  	terminalOpt terminalOpt
    34  
    35  	ctx       context.Context // erow general context
    36  	cancelCtx context.CancelFunc
    37  
    38  	cmd struct {
    39  		sync.Mutex
    40  		cancelInternalCmd context.CancelFunc
    41  		cancelContentCmd  context.CancelFunc
    42  	}
    43  }
    44  
    45  //----------
    46  
    47  func NewLoadedERow(info *ERowInfo, rowPos *ui.RowPos) (*ERow, error) {
    48  	switch {
    49  	case info.IsSpecial():
    50  		return newLoadedSpecialERow(info, rowPos)
    51  	case info.IsDir():
    52  		return newLoadedDirERow(info, rowPos)
    53  	case info.IsFileButNotDir():
    54  		return newLoadedFileERow(info, rowPos)
    55  	default:
    56  		err := fmt.Errorf("unable to open erow: %v", info.name)
    57  		if info.fiErr != nil {
    58  			err = fmt.Errorf("%v: %w", err, info.fiErr)
    59  		}
    60  		return nil, err
    61  	}
    62  }
    63  
    64  // Allows creating rows in place even if a file/dir doesn't exist anymore (ex: show non-existent files rows in a saved session).
    65  func NewLoadedERowOrNewBasic(info *ERowInfo, rowPos *ui.RowPos) *ERow {
    66  	erow, err := NewLoadedERow(info, rowPos)
    67  	if err != nil {
    68  		return NewBasicERow(info, rowPos)
    69  	}
    70  	return erow
    71  }
    72  
    73  //----------
    74  
    75  func ExistingERowOrNewLoaded(ed *Editor, name string) (_ *ERow, isNew bool, _ error) {
    76  	info := ed.ReadERowInfo(name)
    77  	if erow0, ok := info.FirstERow(); ok {
    78  		return erow0, false, nil
    79  	}
    80  	rowPos := ed.GoodRowPos()
    81  	erow, err := NewLoadedERow(info, rowPos)
    82  	if err != nil {
    83  		return nil, false, err
    84  	}
    85  	return erow, true, nil
    86  }
    87  
    88  // Used for ex. in: +messages, +sessions.
    89  func ExistingERowOrNewBasic(ed *Editor, name string) (_ *ERow, isNew bool) {
    90  
    91  	info := ed.ReadERowInfo(name)
    92  	if erow0, ok := info.FirstERow(); ok {
    93  		return erow0, false
    94  	}
    95  	rowPos := ed.GoodRowPos()
    96  	erow := NewBasicERow(info, rowPos)
    97  	return erow, true
    98  }
    99  
   100  //----------
   101  
   102  func NewBasicERow(info *ERowInfo, rowPos *ui.RowPos) *ERow {
   103  	erow := &ERow{}
   104  	erow.init(info, rowPos)
   105  	return erow
   106  }
   107  
   108  func (erow *ERow) init(info *ERowInfo, rowPos *ui.RowPos) {
   109  	erow.Ed = info.Ed
   110  	erow.Info = info
   111  	erow.Row = rowPos.Column.NewRowBefore(rowPos.NextRow)
   112  	erow.Exec = NewERowExec(erow)
   113  
   114  	ctx0 := context.Background() // TODO: editor ctx
   115  	erow.ctx, erow.cancelCtx = context.WithCancel(ctx0)
   116  
   117  	erow.setupSyntaxHighlightAndCommentShortcuts()
   118  	erow.initHandlers()
   119  
   120  	erow.updateToolbarNameEncoding2("")
   121  
   122  	// editor events
   123  	ev := &PostNewERowEEvent{ERow: erow}
   124  	erow.Ed.EEvents.emit(PostNewERowEEventId, ev)
   125  }
   126  
   127  //----------
   128  
   129  func newLoadedSpecialERow(info *ERowInfo, rowPos *ui.RowPos) (*ERow, error) {
   130  	// there can be only one instance of a special row
   131  	if len(info.ERows) > 0 {
   132  		return nil, fmt.Errorf("special row already exists: %v", info.Name())
   133  
   134  	}
   135  	erow := NewBasicERow(info, rowPos)
   136  	// load
   137  	switch {
   138  	case info.Name() == "+Sessions":
   139  		ListSessions(erow.Ed)
   140  	}
   141  	return erow, nil
   142  }
   143  
   144  func newLoadedDirERow(info *ERowInfo, rowPos *ui.RowPos) (*ERow, error) {
   145  	if !info.IsDir() {
   146  		return nil, fmt.Errorf("not a directory")
   147  	}
   148  	erow := NewBasicERow(info, rowPos)
   149  	// load
   150  	ListDirERow(erow, erow.Info.Name(), false, true)
   151  	return erow, nil
   152  }
   153  
   154  func newLoadedFileERow(info *ERowInfo, rowPos *ui.RowPos) (*ERow, error) {
   155  	// read content from existing row
   156  	if erow0, ok := info.FirstERow(); ok {
   157  		// create erow first to get it updated
   158  		erow := NewBasicERow(info, rowPos)
   159  		// update the new erow with content
   160  		info.setRWFromMaster(erow0)
   161  		return erow, nil
   162  	}
   163  
   164  	// load
   165  	b, err := info.readFsFile()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	// update data
   171  	info.setSavedHash(info.fileData.fs.hash, len(b))
   172  
   173  	// new erow (no other rows exist)
   174  	erow := NewBasicERow(info, rowPos)
   175  	erow.Row.TextArea.SetBytesClearHistory(b)
   176  
   177  	return erow, nil
   178  }
   179  
   180  //----------
   181  
   182  func (erow *ERow) Reload() {
   183  	if err := erow.reload(); err != nil {
   184  		erow.Ed.Error(err)
   185  	}
   186  }
   187  
   188  func (erow *ERow) reload() error {
   189  	switch {
   190  	case erow.Info.IsSpecial() && erow.Info.Name() == "+Sessions":
   191  		ListSessions(erow.Ed)
   192  		return nil
   193  	case erow.Info.IsDir():
   194  		ListDirERow(erow, erow.Info.Name(), false, true)
   195  		return nil
   196  	case erow.Info.IsFileButNotDir():
   197  		return erow.Info.ReloadFile()
   198  	default:
   199  		return errors.New("unexpected type to reload")
   200  	}
   201  }
   202  
   203  //----------
   204  
   205  func (erow *ERow) initHandlers() {
   206  	row := erow.Row
   207  
   208  	// register with the editor
   209  	erow.Ed.SetERowInfo(erow.Info.Name(), erow.Info)
   210  	erow.Info.AddERow(erow)
   211  
   212  	// update row state
   213  	erow.Info.UpdateDuplicateRowState()
   214  	erow.Info.UpdateDuplicateHighlightRowState()
   215  	erow.Info.UpdateExistsRowState()
   216  	erow.Info.UpdateFsDifferRowState()
   217  
   218  	// register with watcher
   219  	if !erow.Info.IsSpecial() && len(erow.Info.ERows) == 1 {
   220  		erow.Ed.Watcher.Add(erow.Info.Name())
   221  	}
   222  
   223  	// toolbar on prewrite
   224  	row.Toolbar.RWEvReg.Add(iorw.RWEvIdPreWrite, func(ev0 interface{}) {
   225  		ev := ev0.(*iorw.RWEvPreWrite)
   226  		if err := erow.validateToolbarPreWrite(ev); err != nil {
   227  			ev.ReplyErr = err
   228  		}
   229  	})
   230  	// toolbar cmds
   231  	row.Toolbar.EvReg.Add(ui.TextAreaCmdEventId, func(ev0 interface{}) {
   232  		InternalCmdFromRowTb(erow)
   233  	})
   234  	// textarea on write
   235  	row.TextArea.RWEvReg.Add(iorw.RWEvIdWrite2, func(ev0 interface{}) {
   236  		ev := ev0.(*iorw.RWEvWrite2)
   237  		erow.Info.HandleRWEvWrite2(erow, ev)
   238  	})
   239  	// textarea content cmds
   240  	row.TextArea.EvReg.Add(ui.TextAreaCmdEventId, func(ev0 interface{}) {
   241  		ev := ev0.(*ui.TextAreaCmdEvent)
   242  		ContentCmdFromTextArea(erow, ev.Index)
   243  	})
   244  	// textarea select annotation
   245  	row.TextArea.EvReg.Add(ui.TextAreaSelectAnnotationEventId, func(ev interface{}) {
   246  		ev2 := ev.(*ui.TextAreaSelectAnnotationEvent)
   247  		erow.Ed.GoDebug.SelectERowAnnotation(erow, ev2)
   248  	})
   249  	// textarea inlinecomplete
   250  	row.TextArea.EvReg.Add(ui.TextAreaInlineCompleteEventId, func(ev0 interface{}) {
   251  		ev := ev0.(*ui.TextAreaInlineCompleteEvent)
   252  		handled := erow.Ed.InlineComplete.Complete(erow, ev)
   253  		// Allow the input event (`tab` key press) to function normally if the inlinecomplete is not being handled (ex: no lsproto server is registered for this filename extension)
   254  		ev.ReplyHandled = event.Handled(handled)
   255  	})
   256  	// key shortcuts
   257  	row.EvReg.Add(ui.RowInputEventId, func(ev0 interface{}) {
   258  		erow.Ed.InlineComplete.CancelOnCursorChange()
   259  
   260  		ev := ev0.(*ui.RowInputEvent)
   261  		switch evt := ev.Event.(type) {
   262  		case *event.KeyDown:
   263  			// activate row
   264  			erow.Info.UpdateActiveRowState(erow)
   265  			// shortcuts
   266  			mods := evt.Mods.ClearLocks()
   267  			switch {
   268  			case mods.Is(event.ModCtrl) && evt.KeySym == event.KSymS:
   269  				if err := erow.Info.SaveFile(); err != nil {
   270  					erow.Ed.Error(err)
   271  				}
   272  			case mods.Is(event.ModCtrl) && evt.KeySym == event.KSymF:
   273  				FindShortcut(erow)
   274  			case mods.Is(event.ModCtrl) && evt.KeySym == event.KSymH:
   275  				ReplaceShortcut(erow)
   276  			case mods.Is(event.ModCtrl) && evt.KeySym == event.KSymN:
   277  				NewFileShortcut(erow)
   278  			case mods.Is(event.ModCtrl) && evt.KeySym == event.KSymW:
   279  				row.Close()
   280  			case evt.KeySym == event.KSymEscape:
   281  				erow.Exec.Stop()
   282  			}
   283  		case *event.MouseDown:
   284  			erow.Info.UpdateActiveRowState(erow)
   285  		case *event.MouseEnter:
   286  			erow.highlightDuplicates = true
   287  			erow.Info.UpdateDuplicateHighlightRowState()
   288  		case *event.MouseLeave:
   289  			erow.highlightDuplicates = false
   290  			erow.Info.UpdateDuplicateHighlightRowState()
   291  		}
   292  	})
   293  	// close
   294  	row.EvReg.Add(ui.RowCloseEventId, func(ev0 interface{}) {
   295  		// editor events
   296  		ev := &PreRowCloseEEvent{ERow: erow}
   297  		erow.Ed.EEvents.emit(PreRowCloseEEventId, ev)
   298  
   299  		// cancel general context
   300  		erow.cancelCtx()
   301  
   302  		// ensure execution (if any) is stopped
   303  		erow.Exec.Stop()
   304  
   305  		// unregister from editor
   306  		erow.Info.RemoveERow(erow)
   307  		if len(erow.Info.ERows) == 0 {
   308  			erow.Ed.DeleteERowInfo(erow.Info.Name())
   309  		}
   310  
   311  		// update row state
   312  		erow.Info.UpdateDuplicateRowState()
   313  		erow.Info.UpdateDuplicateHighlightRowState()
   314  
   315  		// unregister with watcher
   316  		if !erow.Info.IsSpecial() && len(erow.Info.ERows) == 0 {
   317  			erow.Ed.Watcher.Remove(erow.Info.Name())
   318  		}
   319  
   320  		// add to reopener to allow to reopen later if needed
   321  		if !erow.Info.IsSpecial() {
   322  			erow.Ed.RowReopener.Add(row)
   323  		}
   324  	})
   325  }
   326  
   327  //----------
   328  
   329  func (erow *ERow) encodedName() string {
   330  	return erow.Ed.HomeVars.Encode(erow.Info.Name())
   331  }
   332  
   333  //----------
   334  
   335  func (erow *ERow) validateToolbarPreWrite(ev *iorw.RWEvPreWrite) error {
   336  	// current content (pre write) copy
   337  	b, err := iorw.ReadFullCopy(erow.Row.Toolbar.RW())
   338  	if err != nil {
   339  		return err
   340  	}
   341  
   342  	// simulate the write
   343  	// TODO: how to guarantee the simulation is accurate and no rw filter exists.
   344  	rw := iorw.NewBytesReadWriterAt(b)
   345  	if err := rw.OverwriteAt(ev.Index, ev.N, ev.P); err != nil {
   346  		return err
   347  	}
   348  	b2, err := iorw.ReadFastFull(rw)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	tbStr2 := string(b2)
   353  
   354  	// simulation name
   355  	data := toolbarparser.Parse(tbStr2)
   356  	arg0, ok := data.Part0Arg0()
   357  	if !ok {
   358  		return fmt.Errorf("unable to get toolbar name")
   359  	}
   360  	simName := arg0.UnquotedString()
   361  
   362  	// expected name
   363  	nameEnc := erow.encodedName()
   364  
   365  	if simName != nameEnc {
   366  		return fmt.Errorf("can't change toolbar name: %q -> %q", nameEnc, simName)
   367  	}
   368  
   369  	// valid data
   370  	erow.TbData = *data
   371  	erow.parseToolbarVars()
   372  
   373  	return nil
   374  }
   375  
   376  //----------
   377  
   378  func (erow *ERow) UpdateToolbarNameEncoding() {
   379  	str := erow.Row.Toolbar.Str()
   380  	erow.updateToolbarNameEncoding2(str)
   381  }
   382  
   383  func (erow *ERow) updateToolbarNameEncoding2(str string) {
   384  	arg0End := 0
   385  	data := toolbarparser.Parse(str)
   386  	arg0, ok := data.Part0Arg0()
   387  	if ok {
   388  		arg0End = arg0.End()
   389  	}
   390  
   391  	// replace part0 arg0 with encoded name
   392  	ename := erow.encodedName()
   393  	str2 := ename + str[arg0End:]
   394  	if str2 != str {
   395  		erow.Row.Toolbar.SetStrClearHistory(str2)
   396  	}
   397  }
   398  
   399  func (erow *ERow) ToolbarSetStrAfterNameClearHistory(s string) {
   400  	arg0, ok := erow.TbData.Part0Arg0()
   401  	if !ok {
   402  		return
   403  	}
   404  	str := erow.Row.Toolbar.Str()[:arg0.End()] + s
   405  	erow.Row.Toolbar.SetStrClearHistory(str)
   406  }
   407  
   408  // ----------
   409  func (erow *ERow) parseToolbarVars() {
   410  	vmap := toolbarparser.ParseVars(&erow.TbData)
   411  
   412  	// $font
   413  	clear := true
   414  	if v, ok := vmap["$font"]; ok {
   415  		err := erow.setVarFontTheme(v)
   416  		if err == nil {
   417  			clear = false
   418  		}
   419  	}
   420  	if clear {
   421  		erow.Row.TextArea.SetThemeFontFace(nil)
   422  	}
   423  
   424  	// $terminal
   425  	erow.terminalOpt = terminalOpt{}
   426  	if erow.Info.IsDir() {
   427  		// Deprecated: use $terminal
   428  		if _, ok := vmap["$termFilter"]; ok {
   429  			erow.terminalOpt.filter = true
   430  		}
   431  
   432  		if v, ok := vmap["$terminal"]; ok {
   433  			u := strings.Split(v, ",")
   434  			for _, k := range u {
   435  				switch k {
   436  				case "f":
   437  					erow.terminalOpt.filter = true
   438  				case "k":
   439  					erow.terminalOpt.keyEvents = true
   440  				}
   441  			}
   442  		}
   443  	}
   444  }
   445  
   446  func (erow *ERow) setVarFontTheme(s string) error {
   447  	w := strings.SplitN(s, ",", 2)
   448  	name := w[0]
   449  
   450  	// font size arg
   451  	size := float64(0)
   452  	if len(w) > 1 {
   453  		v, err := strconv.ParseFloat(w[1], 64)
   454  		if err != nil {
   455  			// commented: ignore error
   456  			//return err
   457  		} else {
   458  			size = v
   459  		}
   460  	}
   461  
   462  	ff, err := ui.ThemeFontFace2(name, size)
   463  	if err != nil {
   464  		return err
   465  	}
   466  	erow.Row.TextArea.SetThemeFontFace(ff)
   467  	return nil
   468  }
   469  
   470  //----------
   471  
   472  // Not UI safe.
   473  func (erow *ERow) TextAreaAppendBytes(p []byte) {
   474  	ta := erow.Row.TextArea
   475  	if err := ta.AppendBytesClearHistory(p); err != nil {
   476  		erow.Ed.Error(err)
   477  	}
   478  }
   479  
   480  // UI safe, with option to wait.
   481  func (erow *ERow) TextAreaAppendBytesAsync(p []byte) func() {
   482  	var wg sync.WaitGroup
   483  	wg.Add(1)
   484  	erow.Ed.UI.RunOnUIGoRoutine(func() {
   485  		erow.TextAreaAppendBytes(p)
   486  		wg.Done()
   487  	})
   488  	return wg.Wait
   489  }
   490  
   491  //----------
   492  
   493  func (erow *ERow) TextAreaReadWriteCloser() io.ReadWriteCloser {
   494  	if erow.terminalOpt.On() {
   495  		return NewTerminalFilter(erow)
   496  	}
   497  
   498  	// synced writer to slow down memory usage
   499  	w := iout.FnWriter(func(b []byte) (int, error) {
   500  		var err error
   501  		erow.Ed.UI.WaitRunOnUIGoRoutine(func() {
   502  			err = erow.Row.TextArea.AppendBytesClearHistory(b)
   503  		})
   504  		return len(b), err
   505  	})
   506  
   507  	// buffered for performance, which needs timed output (auto-flush)
   508  	wc := iout.NewAutoBufWriter(w, 4096*2)
   509  
   510  	rd := iout.FnReader(func(b []byte) (int, error) { return 0, io.EOF })
   511  	type iorwc struct {
   512  		io.Reader
   513  		io.Writer
   514  		io.Closer
   515  	}
   516  	return io.ReadWriteCloser(&iorwc{rd, wc, wc})
   517  }
   518  
   519  //----------
   520  
   521  // UI Safe
   522  func (erow *ERow) Flash() {
   523  	p, ok := erow.TbData.PartAtIndex(0)
   524  	if ok {
   525  		if len(p.Args) > 0 {
   526  			a := p.Args[0]
   527  			erow.Row.Toolbar.FlashIndexLen(a.Pos(), a.End()-a.Pos())
   528  		}
   529  	}
   530  }
   531  
   532  //----------
   533  
   534  func (erow *ERow) MakeIndexVisibleAndFlash(index int) {
   535  	erow.MakeRangeVisibleAndFlash(index, 0)
   536  }
   537  
   538  func (erow *ERow) MakeRangeVisibleAndFlash(index int, len int) {
   539  	// Commented: don't flicker row positions
   540  	//erow.Row.EnsureTextAreaMinimumHeight()
   541  
   542  	erow.Row.EnsureOneToolbarLineYVisible()
   543  
   544  	erow.Row.TextArea.MakeRangeVisible(index, len)
   545  	erow.Row.TextArea.FlashIndexLen(index, len)
   546  
   547  	// flash toolbar as last resort if less visible
   548  	ta := erow.Row.TextArea
   549  	lh := ta.LineHeight()
   550  	min := int(float64(lh) * 1.5)
   551  	if ta.Bounds.Dy() < min {
   552  		erow.Flash()
   553  	}
   554  }
   555  
   556  //----------
   557  
   558  func (erow *ERow) setupSyntaxHighlightAndCommentShortcuts() {
   559  	// special handling for the toolbar (allow comment shortcut to work in the toolbar to easily disable cmds)
   560  	tb := erow.Row.Toolbar
   561  	tb.SetCommentStrings("#")
   562  
   563  	ta := erow.Row.TextArea
   564  
   565  	// ensure syntax highlight is on (ex: strings)
   566  	ta.EnableSyntaxHighlight(true)
   567  
   568  	//// consider only files from here (dirs and special rows are out)
   569  	//if !erow.Info.IsFileButNotDir() {
   570  	//	return
   571  	//}
   572  
   573  	// util funcs
   574  	setComments := func(a ...interface{}) {
   575  		ta.SetCommentStrings(a...)
   576  	}
   577  
   578  	// ignore "." on files starting with "."
   579  	name := filepath.Base(erow.Info.Name())
   580  	if len(name) >= 1 && name[0] == '.' {
   581  		name = name[1:]
   582  	}
   583  
   584  	// specific names
   585  	switch name {
   586  	case "bashrc":
   587  		setComments("#")
   588  		return
   589  	case "go.mod":
   590  		setComments("//")
   591  		return
   592  	}
   593  
   594  	// setup comments based on name extension
   595  	ext := strings.ToLower(filepath.Ext(name))
   596  	switch ext {
   597  	case ".sh",
   598  		".conf", ".list",
   599  		".py", // python
   600  		".pl": // perl
   601  		setComments("#")
   602  	case ".go",
   603  		".c", ".h",
   604  		".cpp", ".hpp", ".cxx", ".hxx", // c++
   605  		".java",
   606  		".v",  // verilog
   607  		".js": // javascript
   608  		setComments("//", [2]string{"/*", "*/"})
   609  	case ".ledger":
   610  		setComments(";", "//")
   611  	case ".pro": // prolog
   612  		setComments("%", [2]string{"/*", "*/"})
   613  	case ".html", ".xml", ".svg":
   614  		setComments([2]string{"<!--", "-->"})
   615  	case ".s", ".asm": // assembly
   616  		setComments("//")
   617  	case ".json":
   618  		// no comments to setup
   619  	case ".txt":
   620  		setComments("#") // useful (but not correct)
   621  	case "": // no file extension
   622  		// handy for ex: /etc/network/interfaces
   623  		setComments("#") // useful (but not correct)
   624  	default: // all other file extensions
   625  		setComments("#") // useful (but not correct)
   626  	}
   627  }
   628  
   629  //----------
   630  
   631  func (erow *ERow) newContentCmdCtx() (context.Context, context.CancelFunc) {
   632  	erow.cmd.Lock()
   633  	defer erow.cmd.Unlock()
   634  	erow.cancelContentCmd2()
   635  	ctx, cancel := context.WithCancel(erow.ctx)
   636  	erow.cmd.cancelContentCmd = cancel
   637  	return ctx, cancel
   638  }
   639  func (erow *ERow) CancelContentCmd() {
   640  	erow.cmd.Lock()
   641  	defer erow.cmd.Unlock()
   642  	erow.cancelContentCmd2()
   643  }
   644  func (erow *ERow) cancelContentCmd2() {
   645  	if erow.cmd.cancelContentCmd != nil {
   646  		erow.cmd.cancelContentCmd()
   647  	}
   648  }
   649  
   650  //----------
   651  
   652  func (erow *ERow) newInternalCmdCtx() (context.Context, context.CancelFunc) {
   653  	erow.cmd.Lock()
   654  	defer erow.cmd.Unlock()
   655  	erow.cancelInternalCmd2()
   656  	ctx, cancel := context.WithCancel(erow.ctx)
   657  	erow.cmd.cancelInternalCmd = cancel
   658  	return ctx, cancel
   659  }
   660  
   661  func (erow *ERow) CancelInternalCmd() {
   662  	erow.cmd.Lock()
   663  	defer erow.cmd.Unlock()
   664  	erow.cancelInternalCmd2()
   665  }
   666  func (erow *ERow) cancelInternalCmd2() {
   667  	if erow.cmd.cancelInternalCmd != nil {
   668  		erow.cmd.cancelInternalCmd()
   669  	}
   670  }
   671  
   672  //----------
   673  
   674  type terminalOpt struct {
   675  	filter    bool
   676  	keyEvents bool
   677  }
   678  
   679  func (t *terminalOpt) On() bool {
   680  	return t.filter || t.keyEvents
   681  }