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

     1  package core
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/jmigpin/editor/core/toolbarparser"
    13  	"github.com/jmigpin/editor/ui"
    14  	"github.com/jmigpin/editor/util/mathutil"
    15  	"github.com/jmigpin/editor/util/osutil"
    16  )
    17  
    18  type Sessions struct {
    19  	Sessions []*Session
    20  }
    21  
    22  func NewSessions(filename string) (*Sessions, error) {
    23  	// read file
    24  	f, err := os.Open(filename)
    25  	if err != nil {
    26  		if os.IsNotExist(err) {
    27  			// empty sessions if it doesn't exist
    28  			return &Sessions{}, nil
    29  		}
    30  		return nil, err
    31  	}
    32  	defer f.Close()
    33  	// decode
    34  	dec := json.NewDecoder(f)
    35  	ss := &Sessions{}
    36  	err = dec.Decode(ss)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	return ss, err
    41  }
    42  func (ss *Sessions) save(filename string) error {
    43  	flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
    44  	f, err := os.OpenFile(filename, flags, 0644)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	defer f.Close()
    49  	enc := json.NewEncoder(f)
    50  	enc.SetIndent("", "\t")
    51  	return enc.Encode(&ss)
    52  }
    53  
    54  //----------
    55  
    56  func sessionsFilename() string {
    57  	home := osutil.HomeEnvVar()
    58  	return filepath.Join(home, ".editor_sessions.json")
    59  }
    60  
    61  //----------
    62  
    63  type Session struct {
    64  	Name      string
    65  	RootTbStr string
    66  	Columns   []*ColumnState
    67  }
    68  
    69  func NewSessionFromEditor(ed *Editor) *Session {
    70  	s := &Session{
    71  		RootTbStr: ed.UI.Root.Toolbar.Str(),
    72  	}
    73  	for _, c := range ed.UI.Root.Cols.Columns() {
    74  		cstate := NewColumnState(ed, c)
    75  		s.Columns = append(s.Columns, cstate)
    76  	}
    77  	return s
    78  }
    79  func (s *Session) restore(ed *Editor) {
    80  	uicols := ed.UI.Root.Cols
    81  
    82  	// layout toolbar
    83  	tbStr := s.RootTbStr
    84  
    85  	ed.UI.Root.Toolbar.SetStrClearHistory(tbStr)
    86  
    87  	// close all current columns
    88  	for _, c := range uicols.Columns() {
    89  		c.Close()
    90  	}
    91  
    92  	// open n new columns
    93  	// allow other columns to exist already (ex: on close, the editor could be ensuring one column)
    94  	for len(uicols.Columns()) < len(s.Columns) {
    95  		_ = ed.NewColumn()
    96  	}
    97  
    98  	// setup columns sizes (end percents)
    99  	uicolumns := uicols.Columns()
   100  	for i, c := range s.Columns {
   101  		sp := c.StartPercent
   102  
   103  		uicols.ColsLayout.Spl.SetRawStartPercent(uicolumns[i], sp)
   104  	}
   105  
   106  	// create rows
   107  	m := make(map[*RowState]*ERow)
   108  	for i, c := range s.Columns {
   109  		uicol := uicolumns[i]
   110  
   111  		for _, rs := range c.Rows {
   112  			rowPos := &ui.RowPos{Column: uicol}
   113  			erow, ok, err := rs.OpenERow(ed, rowPos)
   114  			if err != nil {
   115  				ed.Error(err)
   116  			}
   117  			if ok {
   118  				m[rs] = erow
   119  
   120  				// setup row size
   121  				sp := rs.StartPercent
   122  
   123  				uicol.RowsLayout.Spl.SetRawStartPercent(erow.Row, sp)
   124  			}
   125  		}
   126  	}
   127  
   128  	// restore positions after positioning rows to have correct dimensions
   129  	for rs, erow := range m {
   130  		rs.RestorePos(erow)
   131  	}
   132  }
   133  
   134  //----------
   135  
   136  type ColumnState struct {
   137  	StartPercent float64
   138  	Rows         []*RowState
   139  }
   140  
   141  func NewColumnState(ed *Editor, col *ui.Column) *ColumnState {
   142  	cstate := &ColumnState{
   143  		StartPercent: roundStartPercent(col.Cols.ColsLayout.Spl.RawStartPercent(col)),
   144  	}
   145  	for _, row := range col.Rows() {
   146  		rstate := NewRowState(ed, row)
   147  		cstate.Rows = append(cstate.Rows, rstate)
   148  	}
   149  	return cstate
   150  }
   151  
   152  //----------
   153  
   154  // Used in sessions and reopenrow.
   155  type RowState struct {
   156  	TbStr         string
   157  	TbCursorIndex int
   158  	TaCursorIndex int
   159  	TaOffsetIndex int
   160  	StartPercent  float64
   161  }
   162  
   163  func NewRowState(ed *Editor, row *ui.Row) *RowState {
   164  	// get toolbar string with the name decoded
   165  	tbStr := row.Toolbar.Str()
   166  	data := toolbarparser.Parse(tbStr)
   167  	arg0, ok := data.Part0Arg0()
   168  	if ok {
   169  		arg0Str := arg0.String()
   170  		name := ed.HomeVars.Decode(arg0Str)
   171  		tbStr = name + tbStr[len(arg0Str):]
   172  	}
   173  
   174  	rs := &RowState{
   175  		TbStr:         tbStr,
   176  		TbCursorIndex: row.Toolbar.CursorIndex(),
   177  		TaCursorIndex: row.TextArea.CursorIndex(),
   178  		TaOffsetIndex: row.TextArea.RuneOffset(),
   179  	}
   180  
   181  	// check row.col in case the row has been removed from columns (reopenrow?)
   182  	if row.Col != nil {
   183  		rs.StartPercent = roundStartPercent(row.Col.RowsLayout.Spl.RawStartPercent(row))
   184  	}
   185  
   186  	return rs
   187  }
   188  
   189  func (state *RowState) OpenERow(ed *Editor, rowPos *ui.RowPos) (*ERow, bool, error) {
   190  	data := toolbarparser.Parse(state.TbStr)
   191  	arg0, ok := data.Part0Arg0()
   192  	if !ok {
   193  		return nil, false, fmt.Errorf("missing toolbar arg 0: %s", state.TbStr)
   194  	}
   195  
   196  	name := ed.HomeVars.Decode(arg0.String())
   197  	info := ed.ReadERowInfo(name)
   198  
   199  	// create erow, even if it had have errors
   200  	erow := NewLoadedERowOrNewBasic(info, rowPos)
   201  
   202  	// setup toolbar even if erow had errors
   203  	w := data.Str[arg0.End():]
   204  	if strings.TrimSpace(w) != "" {
   205  		erow.ToolbarSetStrAfterNameClearHistory(w)
   206  	}
   207  
   208  	return erow, true, nil
   209  }
   210  
   211  func (state *RowState) RestorePos(erow *ERow) {
   212  	erow.Row.Toolbar.SetCursorIndex(state.TbCursorIndex)
   213  	erow.Row.TextArea.SetCursorIndex(state.TaCursorIndex)
   214  	erow.Row.TextArea.SetRuneOffset(state.TaOffsetIndex)
   215  }
   216  
   217  //----------
   218  
   219  func SaveSession(ed *Editor, part *toolbarparser.Part) {
   220  	err := saveSession(ed, part, sessionsFilename())
   221  	if err != nil {
   222  		ed.Error(err)
   223  	}
   224  }
   225  func saveSession(ed *Editor, part *toolbarparser.Part, filename string) error {
   226  	if len(part.Args) != 2 {
   227  		return fmt.Errorf("savesession: missing session name")
   228  	}
   229  	sessionName := part.Args[1].String()
   230  
   231  	s1 := NewSessionFromEditor(ed)
   232  	s1.Name = sessionName
   233  
   234  	ss, err := NewSessions(filename)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	// replace session already stored
   239  	replaced := false
   240  	for i, s := range ss.Sessions {
   241  		if s.Name == sessionName {
   242  			ss.Sessions[i] = s1
   243  			replaced = true
   244  			break
   245  		}
   246  	}
   247  	// append if a new session
   248  	if !replaced {
   249  		ss.Sessions = append(ss.Sessions, s1)
   250  	}
   251  	// save to file
   252  	err = ss.save(filename)
   253  	if err != nil {
   254  		return err
   255  	}
   256  	return nil
   257  }
   258  
   259  //----------
   260  
   261  func ListSessions(ed *Editor) {
   262  	ss, err := NewSessions(sessionsFilename())
   263  	if err != nil {
   264  		ed.Error(err)
   265  		return
   266  	}
   267  
   268  	// sort sessions names
   269  	var u []string
   270  	for _, session := range ss.Sessions {
   271  		u = append(u, session.Name)
   272  	}
   273  	sort.Strings(u)
   274  
   275  	// concat opensession lines
   276  	buf := &bytes.Buffer{}
   277  	fmt.Fprintf(buf, "sessions: %d\n", len(u))
   278  	for _, sname := range u {
   279  		fmt.Fprintf(buf, "OpenSession %v\n", sname)
   280  	}
   281  
   282  	erow, _ := ExistingERowOrNewBasic(ed, "+Sessions")
   283  	erow.Row.TextArea.SetBytesClearPos(buf.Bytes())
   284  	erow.Flash()
   285  }
   286  
   287  //----------
   288  
   289  func OpenSession(ed *Editor, part *toolbarparser.Part) {
   290  	if len(part.Args) != 2 {
   291  		ed.Errorf("missing session name")
   292  		return
   293  	}
   294  	sessionName := part.Args[1].String()
   295  	OpenSessionFromString(ed, sessionName)
   296  }
   297  
   298  func OpenSessionFromString(ed *Editor, sessionName string) {
   299  	ss, err := NewSessions(sessionsFilename())
   300  	if err != nil {
   301  		return
   302  	}
   303  	for _, s := range ss.Sessions {
   304  		if s.Name == sessionName {
   305  			s.restore(ed)
   306  			return
   307  		}
   308  	}
   309  	ed.Errorf("session not found: %v", sessionName)
   310  }
   311  
   312  //----------
   313  
   314  func DeleteSession(ed *Editor, part *toolbarparser.Part) {
   315  	err := deleteSession(ed, part)
   316  	if err != nil {
   317  		ed.Error(err)
   318  	}
   319  }
   320  func deleteSession(ed *Editor, part *toolbarparser.Part) error {
   321  	if len(part.Args) != 2 {
   322  		return fmt.Errorf("deletesession: missing session name")
   323  	}
   324  	sessionName := part.Args[1].String()
   325  	ss, err := NewSessions(sessionsFilename())
   326  	if err != nil {
   327  		return err
   328  	}
   329  	found := false
   330  	for i, s := range ss.Sessions {
   331  		if s.Name == sessionName {
   332  			found = true
   333  			u := ss.Sessions
   334  			ss.Sessions = append(u[:i], u[i+1:]...)
   335  			break
   336  		}
   337  	}
   338  	if !found {
   339  		return fmt.Errorf("deletesession: session not found: %v", sessionName)
   340  	}
   341  	return ss.save(sessionsFilename())
   342  }
   343  
   344  //----------
   345  
   346  func roundStartPercent(v float64) float64 {
   347  	return mathutil.RoundFloat64(v, 8)
   348  }