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  }