1package main 2 3import ( 4 "bytes" 5 "errors" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "github.com/xyproto/env" 13) 14 15var portalFilename = env.ExpandUser(filepath.Join(env.Str("TMPDIR", "/tmp"), env.Str("LOGNAME", "o")+"_portal.txt")) 16 17// Portal is a filename and a line number, for pulling text from 18type Portal struct { 19 absFilename string 20 lineNumber LineNumber 21} 22 23// NewPortal returns a new portal to this filename and line number, 24// but does not save the new portal. Use the Save() method for that. 25func (e *Editor) NewPortal() (*Portal, error) { 26 absFilename, err := e.AbsFilename() 27 if err != nil { 28 return nil, err 29 } 30 return &Portal{absFilename, e.LineNumber()}, nil 31} 32 33// SameFile checks if the portal exist in the same file as the editor is editing 34func (p *Portal) SameFile(e *Editor) bool { 35 absFilename, err := e.AbsFilename() 36 if err != nil { 37 return false 38 } 39 return absFilename == p.absFilename 40} 41 42// MoveDown is useful when using portals within the same file. 43func (p *Portal) MoveDown() { 44 // PopLine handles overflows. 45 p.lineNumber++ 46} 47 48// MoveUp is useful when using portals within the same file. 49func (p *Portal) MoveUp() { 50 // PopLine handles overflows. 51 p.lineNumber-- 52} 53 54// ClosePortal will clear the portal by removing the portal file 55func ClosePortal(e *Editor) error { 56 e.sameFilePortal = nil 57 return os.Remove(portalFilename) 58} 59 60// HasPortal checks if a portal is currently active 61func HasPortal() bool { 62 return exists(portalFilename) 63} 64 65// LoadPortal will load a filename + line number from the portal.txt file 66func LoadPortal() (*Portal, error) { 67 data, err := ioutil.ReadFile(portalFilename) 68 if err != nil { 69 return nil, err 70 } 71 if !bytes.Contains(data, []byte{'\n'}) { 72 return nil, errors.New(portalFilename + " does not have a newline, it's not a portal file") 73 } 74 lines := strings.Split(string(data), "\n") 75 if len(lines) < 2 { 76 return nil, errors.New(portalFilename + " contains too few lines") 77 } 78 absFilename, err := filepath.Abs(lines[0]) 79 if err != nil { 80 return nil, err 81 } 82 lineInt, err := strconv.Atoi(lines[1]) 83 if err != nil { 84 return nil, err 85 } 86 lineNumber := LineNumber(lineInt) 87 return &Portal{absFilename, lineNumber}, nil 88} 89 90// LineIndex returns the current line index that the portal points to 91func (p *Portal) LineIndex() LineIndex { 92 return p.lineNumber.LineIndex() 93} 94 95// LineNumber returns the current line number that the portal points to 96func (p *Portal) LineNumber() LineNumber { 97 return p.lineNumber 98} 99 100// Save will save the portal 101func (p *Portal) Save() error { 102 s := p.absFilename + "\n" + p.lineNumber.String() + "\n" 103 // Anyone can read this file 104 if err := ioutil.WriteFile(portalFilename, []byte(s), 0600); err != nil { 105 return err 106 } 107 return os.Chmod(portalFilename, 0666) 108} 109 110// String returns the current portal (filename + line number) as a colon separated string 111func (p *Portal) String() string { 112 return filepath.Base(p.absFilename) + ":" + p.lineNumber.String() 113} 114 115// NewLineInserted reacts when the editor inserts a new line in the same file, 116// and moves the portal source one line down, if needed. 117func (p *Portal) NewLineInserted(y LineIndex) { 118 if y < p.LineIndex() { 119 p.MoveDown() 120 } 121} 122 123// PopLine removes (!) a line from the portal file, then removes that line 124func (p *Portal) PopLine(e *Editor, removeLine bool) (string, error) { 125 // popping a line from the same file is a special case 126 if p == e.sameFilePortal { 127 if removeLine { 128 return "", errors.New("not implemented") // not implemented and currently not in use 129 } 130 // The line moving is done by the editor InsertAbove and InsertBelow functions 131 return e.Line(p.LineIndex()), nil 132 } 133 data, err := ioutil.ReadFile(p.absFilename) 134 if err != nil { 135 return "", err 136 } 137 lines := strings.Split(string(data), "\n") 138 foundLine := "" 139 found := false 140 if removeLine { 141 modifiedLines := make([]string, 0, len(lines)-1) 142 for i, line := range lines { 143 if LineIndex(i) == p.lineNumber.LineIndex() { 144 foundLine = line 145 found = true 146 } else { 147 modifiedLines = append(modifiedLines, line) 148 } 149 } 150 if !found { 151 return "", errors.New("Could not pop line " + p.String()) 152 } 153 data = []byte(strings.Join(modifiedLines, "\n")) 154 if err = ioutil.WriteFile(p.absFilename, data, 0600); err != nil { 155 return "", err 156 } 157 } else { 158 for i, line := range lines { 159 if LineIndex(i) == p.lineNumber.LineIndex() { 160 foundLine = line 161 found = true 162 break 163 } 164 } 165 if !found { 166 return "", errors.New("Could not pop line " + p.String()) 167 } 168 // Now move the line number +1 169 p.lineNumber++ 170 // And save the new portal 171 if err := p.Save(); err != nil { 172 return foundLine, err 173 } 174 } 175 return foundLine, nil 176} 177