github.com/aretext/aretext@v1.3.0/state/state.go (about) 1 package state 2 3 import ( 4 "github.com/aretext/aretext/cellwidth" 5 "github.com/aretext/aretext/clipboard" 6 "github.com/aretext/aretext/config" 7 "github.com/aretext/aretext/file" 8 "github.com/aretext/aretext/menu" 9 "github.com/aretext/aretext/selection" 10 "github.com/aretext/aretext/syntax" 11 "github.com/aretext/aretext/syntax/parser" 12 "github.com/aretext/aretext/text" 13 "github.com/aretext/aretext/text/segment" 14 "github.com/aretext/aretext/undo" 15 ) 16 17 // EditorState represents the current state of the editor. 18 type EditorState struct { 19 screenWidth, screenHeight uint64 20 configRuleSet config.RuleSet 21 documentLoadCount int 22 inputMode InputMode 23 documentBuffer *BufferState 24 clipboard *clipboard.C 25 fileWatcher *file.Watcher 26 fileTimeline *file.Timeline 27 menu *MenuState 28 textfield *TextFieldState 29 task *TaskState 30 macroState MacroState 31 customMenuItems []menu.Item 32 dirPatternsToHide []string 33 styles map[string]config.StyleConfig 34 statusMsg StatusMsg 35 suspendScreenFunc SuspendScreenFunc 36 quitFlag bool 37 } 38 39 func NewEditorState(screenWidth, screenHeight uint64, configRuleSet config.RuleSet, suspendScreenFunc SuspendScreenFunc) *EditorState { 40 var documentBufferHeight uint64 41 if screenHeight > 0 { 42 // Leave one line for the status bar at the bottom. 43 documentBufferHeight = screenHeight - 1 44 } 45 46 buffer := &BufferState{ 47 textTree: text.NewTree(), 48 cursor: cursorState{}, 49 selector: &selection.Selector{}, 50 view: viewState{ 51 textOrigin: 0, 52 x: 0, 53 y: 0, 54 width: screenWidth, 55 height: documentBufferHeight, 56 }, 57 search: searchState{}, 58 undoLog: undo.NewLog(), 59 syntaxLanguage: syntax.LanguagePlaintext, 60 syntaxParser: nil, 61 lineNumberMode: config.DefaultLineNumberMode, 62 tabSize: uint64(config.DefaultTabSize), 63 tabExpand: config.DefaultTabExpand, 64 showSpaces: config.DefaultShowSpaces, 65 showTabs: config.DefaultShowTabs, 66 autoIndent: config.DefaultAutoIndent, 67 } 68 69 return &EditorState{ 70 screenWidth: screenWidth, 71 screenHeight: screenHeight, 72 configRuleSet: configRuleSet, 73 documentBuffer: buffer, 74 clipboard: clipboard.New(), 75 fileWatcher: file.NewEmptyWatcher(), 76 fileTimeline: file.NewTimeline(), 77 menu: &MenuState{}, 78 textfield: &TextFieldState{}, 79 customMenuItems: nil, 80 dirPatternsToHide: nil, 81 statusMsg: StatusMsg{}, 82 styles: nil, 83 suspendScreenFunc: suspendScreenFunc, 84 } 85 } 86 87 func (s *EditorState) ScreenSize() (uint64, uint64) { 88 return s.screenWidth, s.screenHeight 89 } 90 91 func (s *EditorState) DocumentLoadCount() int { 92 return s.documentLoadCount 93 } 94 95 func (s *EditorState) SetScreenSize(width, height uint64) { 96 s.screenWidth = width 97 s.screenHeight = height 98 } 99 100 func (s *EditorState) InputMode() InputMode { 101 return s.inputMode 102 } 103 104 func (s *EditorState) DocumentBuffer() *BufferState { 105 return s.documentBuffer 106 } 107 108 func (s *EditorState) Menu() *MenuState { 109 return s.menu 110 } 111 112 func (s *EditorState) TextField() *TextFieldState { 113 return s.textfield 114 } 115 116 func (s *EditorState) TaskResultChan() chan func(*EditorState) { 117 if s.task == nil { 118 return nil 119 } 120 return s.task.resultChan 121 } 122 123 func (s *EditorState) IsRecordingUserMacro() bool { 124 return s.macroState.isRecordingUserMacro 125 } 126 127 func (s *EditorState) DirPatternsToHide() []string { 128 return s.dirPatternsToHide 129 } 130 131 func (s *EditorState) StatusMsg() StatusMsg { 132 return s.statusMsg 133 } 134 135 func (s *EditorState) Styles() map[string]config.StyleConfig { 136 return s.styles 137 } 138 139 func (s *EditorState) FileWatcher() *file.Watcher { 140 return s.fileWatcher 141 } 142 143 func (s *EditorState) QuitFlag() bool { 144 return s.quitFlag 145 } 146 147 // BufferState represents the current state of a text buffer. 148 type BufferState struct { 149 textTree *text.Tree 150 cursor cursorState 151 selector *selection.Selector 152 view viewState 153 search searchState 154 undoLog *undo.Log 155 syntaxLanguage syntax.Language 156 syntaxParser *parser.P 157 lineNumberMode config.LineNumberMode 158 tabSize uint64 159 tabExpand bool 160 showTabs bool 161 showSpaces bool 162 autoIndent bool 163 showLineNum bool 164 lineWrapAllowCharBreaks bool 165 } 166 167 func (s *BufferState) TextTree() *text.Tree { 168 return s.textTree 169 } 170 171 func (s *BufferState) SyntaxTokensIntersectingRange(startPos, endPos uint64) []parser.Token { 172 if s.syntaxParser == nil { 173 return nil 174 } 175 return s.syntaxParser.TokensIntersectingRange(startPos, endPos) 176 } 177 178 func (s *BufferState) CursorPosition() uint64 { 179 return s.cursor.position 180 } 181 182 func (s *BufferState) SelectedRegion() selection.Region { 183 return s.selector.Region(s.textTree, s.cursor.position) 184 } 185 186 func (s *BufferState) SelectionMode() selection.Mode { 187 return s.selector.Mode() 188 } 189 190 func (s *BufferState) SelectionEndLocator() Locator { 191 return SelectionEndLocator(s.textTree, s.cursor.position, s.selector) 192 } 193 194 func (s *BufferState) ViewTextOrigin() uint64 { 195 return s.view.textOrigin 196 } 197 198 func (s *BufferState) ViewOrigin() (uint64, uint64) { 199 return s.view.x, s.view.y 200 } 201 202 func (s *BufferState) ViewSize() (uint64, uint64) { 203 return s.view.width, s.view.height 204 } 205 206 func (s *BufferState) SearchQueryAndDirection() (string, SearchDirection) { 207 return s.search.query, s.search.direction 208 } 209 210 func (s *BufferState) SearchMatch() *SearchMatch { 211 return s.search.match 212 } 213 214 func (s *BufferState) SetViewSize(width, height uint64) { 215 s.view.width = width 216 s.view.height = height 217 } 218 219 func (s *BufferState) TabSize() uint64 { 220 return s.tabSize 221 } 222 223 func (s *BufferState) ShowTabs() bool { 224 return s.showTabs 225 } 226 227 func (s *BufferState) ShowSpaces() bool { 228 return s.showSpaces 229 } 230 231 func (s *BufferState) LineNumberMode() config.LineNumberMode { 232 return s.lineNumberMode 233 } 234 235 func (s *BufferState) LineNumMarginWidth() uint64 { 236 if !s.showLineNum { 237 return 0 238 } 239 240 // One column for each digit in the last line number, 241 // plus one space, with a minimum of three cols. 242 width := uint64(1) 243 n := s.textTree.NumLines() 244 for n > 0 { 245 width++ 246 n /= 10 247 } 248 if width < 3 { 249 width = 3 250 } 251 252 // Collapse the line margin column if there isn't enough 253 // space for at least one column of document text. 254 if width >= s.view.width { 255 return 0 256 } 257 258 return width 259 } 260 261 func (s *BufferState) LineWrapConfig() segment.LineWrapConfig { 262 width := s.view.width - s.LineNumMarginWidth() 263 tabSize := s.tabSize 264 gcWidthFunc := func(gc []rune, offsetInLine uint64) uint64 { 265 return cellwidth.GraphemeClusterWidth(gc, offsetInLine, tabSize) 266 } 267 return segment.LineWrapConfig{ 268 MaxLineWidth: width, 269 WidthFunc: gcWidthFunc, 270 AllowCharBreaks: s.lineWrapAllowCharBreaks, 271 } 272 } 273 274 // viewState represents the current view of the document. 275 type viewState struct { 276 // textOrigin is the location in the text tree of the first visible character. 277 textOrigin uint64 278 279 // x and y are the screen coordinates of the top-left corner 280 x, y uint64 281 282 // width and height are the visible width (in columns) and height (in rows) of the document. 283 width, height uint64 284 }