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 }