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

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"path/filepath"
     6  	"strings"
     7  
     8  	"github.com/jmigpin/editor/core/toolbarparser"
     9  	"github.com/jmigpin/editor/ui"
    10  	"github.com/jmigpin/editor/util/uiutil/widget"
    11  )
    12  
    13  //----------
    14  
    15  type InternalCmd struct {
    16  	Name      string
    17  	Fn        InternalCmdFn
    18  	NeedsERow bool
    19  	Detach    bool // run outside UI goroutine (care must be taken)
    20  }
    21  
    22  type InternalCmdFn func(args *InternalCmdArgs) error
    23  
    24  type InternalCmdArgs struct {
    25  	Ctx  context.Context
    26  	Ed   *Editor
    27  	ERow *ERow // could be nil
    28  	Part *toolbarparser.Part
    29  }
    30  
    31  //----------
    32  
    33  // cmds added via init() from "internalcmds" pkg
    34  var InternalCmds = internalCmds{}
    35  
    36  type internalCmds map[string]*InternalCmd
    37  
    38  func (ic *internalCmds) Set(cmd *InternalCmd) {
    39  	(*ic)[cmd.Name] = cmd
    40  }
    41  
    42  //----------
    43  
    44  func InternalCmdFromRootTb(ed *Editor, tb *ui.Toolbar) {
    45  	tbdata := toolbarparser.Parse(tb.Str())
    46  	part, ok := tbdata.PartAtIndex(int(tb.CursorIndex()))
    47  	if !ok {
    48  		ed.Errorf("missing part at index")
    49  		return
    50  	}
    51  	if len(part.Args) == 0 {
    52  		ed.Errorf("part at index has no args")
    53  		return
    54  	}
    55  
    56  	internalCmd(ed, part, nil)
    57  }
    58  
    59  //----------
    60  
    61  func InternalCmdFromRowTb(erow *ERow) {
    62  	part, ok := erow.TbData.PartAtIndex(int(erow.Row.Toolbar.CursorIndex()))
    63  	if !ok {
    64  		erow.Ed.Errorf("missing part at index")
    65  		return
    66  	}
    67  	if len(part.Args) == 0 {
    68  		erow.Ed.Errorf("part at index has no args")
    69  		return
    70  	}
    71  
    72  	// first part cmd
    73  	if part == erow.TbData.Parts[0] {
    74  		if !internalCmdFromRowTbFirstPart(erow, part) {
    75  			erow.Ed.Errorf("no cmd was run")
    76  		}
    77  		return
    78  	}
    79  
    80  	internalCmd(erow.Ed, part, erow)
    81  }
    82  
    83  func internalCmdFromRowTbFirstPart(erow *ERow, part *toolbarparser.Part) bool {
    84  	a0 := part.Args[0]
    85  	ci := erow.Row.Toolbar.CursorIndex()
    86  
    87  	// cursor index beyond arg0
    88  	if ci > a0.End() {
    89  		return false
    90  	}
    91  
    92  	// get path up to cursor index
    93  	a0ci := ci - a0.Pos()
    94  	filename := a0.String()
    95  	i := strings.Index(filename[a0ci:], string(filepath.Separator))
    96  	if i >= 0 {
    97  		filename = filename[:a0ci+i]
    98  	}
    99  
   100  	// decode filename
   101  	filename = erow.Ed.HomeVars.Decode(filename)
   102  
   103  	// create new row
   104  	info := erow.Ed.ReadERowInfo(filename)
   105  	erow2, err := NewLoadedERow(info, erow.Row.PosBelow())
   106  	if err != nil {
   107  		erow.Ed.Error(err)
   108  		return true
   109  	}
   110  
   111  	erow2.Flash()
   112  
   113  	// set same offset if not dir
   114  	if erow2.Info.IsFileButNotDir() {
   115  		ta := erow.Row.TextArea
   116  		ta2 := erow2.Row.TextArea
   117  		ta2.SetCursorIndex(ta.CursorIndex())
   118  		ta2.SetRuneOffset(ta.RuneOffset())
   119  	}
   120  
   121  	return true
   122  }
   123  
   124  //----------
   125  
   126  // erow can be nil (ex: a root toolbar cmd)
   127  func internalCmd(ed *Editor, part *toolbarparser.Part, erow *ERow) {
   128  	arg0 := part.Args[0].UnquotedString()
   129  	noERowErr := func() {
   130  		ed.Errorf("%s: no active row", arg0)
   131  	}
   132  
   133  	// util functions
   134  
   135  	currentERow := func() *ERow {
   136  		if erow != nil {
   137  			return erow
   138  		}
   139  		e, ok := ed.ActiveERow()
   140  		if ok {
   141  			return e
   142  		}
   143  		return nil
   144  	}
   145  	run := func(detach bool, node widget.Node, fn func()) {
   146  		if detach {
   147  			ed.RunAsyncBusyCursor(node, func(done func()) {
   148  				defer done()
   149  				fn()
   150  			})
   151  		} else {
   152  			fn()
   153  		}
   154  	}
   155  
   156  	curERow := currentERow() // possibly != erow, could be nil
   157  
   158  	// internal cmds
   159  	cmd, ok := InternalCmds[arg0]
   160  	if ok {
   161  		ctx := context.Background() // TODO: editor ctx
   162  		args := &InternalCmdArgs{ctx, ed, curERow, part}
   163  		if cmd.NeedsERow && args.ERow == nil {
   164  			noERowErr()
   165  			return
   166  		}
   167  
   168  		// feedback node
   169  		node := widget.Node(ed.UI.Root)
   170  		if erow != nil && args.ERow == erow {
   171  			node = erow.Row
   172  		}
   173  
   174  		run(cmd.Detach, node, func() {
   175  			if cmd.NeedsERow {
   176  				ctx, cancel := args.ERow.newInternalCmdCtx()
   177  				defer cancel()
   178  				args.Ctx = ctx
   179  			}
   180  			if err := cmd.Fn(args); err != nil {
   181  				ed.Errorf("%v: %v", arg0, err)
   182  			}
   183  		})
   184  		return
   185  	}
   186  
   187  	// have a plugin handle the cmd
   188  	handled := ed.Plugins.RunToolbarCmd(curERow, part)
   189  	if handled {
   190  		return
   191  	}
   192  
   193  	// run external cmd (needs erow)
   194  	if curERow == nil {
   195  		noERowErr()
   196  		return
   197  	}
   198  	run(false, curERow.Row, func() {
   199  		ExternalCmd(curERow, part)
   200  	})
   201  }