github.com/grzegorz-zur/bm@v0.0.0-20240312214136-6fc133e3e2c0/file.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 "unicode/utf8" 7 ) 8 9 // File represents open file. 10 type File struct { 11 Path string 12 content string 13 location int 14 mark int 15 area Area 16 time time.Time 17 changed bool 18 history *History 19 } 20 21 const ( 22 // Tab is tabulation rune. 23 Tab = '\t' 24 // EOL is end of line rune. 25 EOL = '\n' 26 ) 27 28 // Archive makes a record in history. 29 func (file *File) Archive() { 30 file.history.Archive(file.content, file.location) 31 } 32 33 // SwitchVersion switches between versions from history. 34 func (file *File) SwitchVersion(dir Direction) { 35 file.content, file.location = file.history.Switch(dir) 36 } 37 38 // Select sets selection position. 39 func (file *File) Select() { 40 file.mark = file.location 41 } 42 43 // Copy returns selected content. 44 func (file *File) Copy() string { 45 content, _, _ := file.copy() 46 return content 47 } 48 49 // Cut cuts selected content. 50 func (file *File) Cut() string { 51 content, from, to := file.copy() 52 file.Remove(from, to) 53 return content 54 } 55 56 func (file *File) copy() (content string, from, to int) { 57 from, to = file.mark, file.location 58 if from > to { 59 from, to = to, from 60 } 61 _, size := file.next(to) 62 to += size 63 return file.content[from:to], from, to 64 } 65 66 // Render renders file content. 67 func (file *File) Render(view *View) error { 68 position := file.position() 69 file.area = file.area.Resize(view.Size).Shift(position) 70 line, column := 0, 0 71 for location, rune := range file.content { 72 if file.area.Contains(Position{line, column}) { 73 rline := line - file.area.Top 74 rcolumn := column - file.area.Left 75 if view.Visible { 76 view.Content[rline][rcolumn] = visible(rune) 77 } else { 78 view.Content[rline][rcolumn] = rune 79 } 80 if view.Select { 81 view.Selection[rline][rcolumn] = file.selected(location) 82 } 83 } 84 column++ 85 if rune == EOL { 86 line++ 87 column = 0 88 } 89 if file.area.Bottom <= line { 90 break 91 } 92 } 93 view.Position = Position{ 94 Line: position.Line - file.area.Top, 95 Column: position.Column - file.area.Left, 96 } 97 view.Status = fmt.Sprintf("%s %d:%d", file.Path, position.Line+1, position.Column+1) 98 view.Cursor = CursorContent 99 return nil 100 } 101 102 func (file *File) position() Position { 103 line, column := 0, 0 104 for location, rune := range file.content { 105 if location == file.location { 106 break 107 } 108 column++ 109 if rune == EOL { 110 line++ 111 column = 0 112 } 113 } 114 return Position{line, column} 115 } 116 117 func (file *File) selected(location int) bool { 118 return file.mark <= location && location < file.location || 119 file.location < location && location <= file.mark 120 } 121 122 func (file *File) last() (rune rune, size int) { 123 return file.previous(file.location) 124 } 125 126 func (file *File) current() (rune rune, size int) { 127 return file.next(file.location) 128 } 129 130 func (file *File) previous(location int) (rune rune, size int) { 131 return utf8.DecodeLastRuneInString(file.content[:location]) 132 } 133 134 func (file *File) next(location int) (rune rune, size int) { 135 return utf8.DecodeRuneInString(file.content[location:]) 136 }