github.com/aretext/aretext@v1.3.0/state/cursor_test.go (about)

     1  package state
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/aretext/aretext/locate"
    10  	"github.com/aretext/aretext/selection"
    11  	"github.com/aretext/aretext/text"
    12  )
    13  
    14  func TestMoveCursor(t *testing.T) {
    15  	textTree, err := text.NewTreeFromString("abcd")
    16  	require.NoError(t, err)
    17  	state := NewEditorState(100, 100, nil, nil)
    18  	state.documentBuffer.textTree = textTree
    19  	state.documentBuffer.cursor.position = 2
    20  	MoveCursor(state, func(params LocatorParams) uint64 {
    21  		return locate.NextCharInLine(params.TextTree, 1, false, params.CursorPos)
    22  	})
    23  	assert.Equal(t, uint64(3), state.documentBuffer.cursor.position)
    24  }
    25  
    26  func TestMoveCursorToLineAbove(t *testing.T) {
    27  	testCases := []struct {
    28  		name           string
    29  		inputString    string
    30  		count          uint64
    31  		initialCursor  cursorState
    32  		expectedCursor cursorState
    33  	}{
    34  		{
    35  			name:           "empty string, move up one line",
    36  			inputString:    "",
    37  			count:          1,
    38  			initialCursor:  cursorState{position: 0},
    39  			expectedCursor: cursorState{position: 0},
    40  		},
    41  		{
    42  			name:           "single line, move up one line",
    43  			inputString:    "abcdefgh",
    44  			count:          1,
    45  			initialCursor:  cursorState{position: 3},
    46  			expectedCursor: cursorState{position: 3},
    47  		},
    48  		{
    49  			name:           "single line, move up one line with logical offset",
    50  			inputString:    "abcdefgh",
    51  			count:          1,
    52  			initialCursor:  cursorState{position: 7, logicalOffset: 4},
    53  			expectedCursor: cursorState{position: 7, logicalOffset: 4},
    54  		},
    55  		{
    56  			name:           "two lines, move up one line at start of line",
    57  			inputString:    "abcdefgh\nijklm\nopqrs",
    58  			count:          1,
    59  			initialCursor:  cursorState{position: 15},
    60  			expectedCursor: cursorState{position: 9},
    61  		},
    62  		{
    63  			name:           "two lines, move up at same offset",
    64  			inputString:    "abcdefgh\nijklmnop",
    65  			count:          1,
    66  			initialCursor:  cursorState{position: 11},
    67  			expectedCursor: cursorState{position: 2},
    68  		},
    69  		{
    70  			name:           "two lines, move up from shorter line to longer line",
    71  			inputString:    "abcdefgh\nijk",
    72  			count:          1,
    73  			initialCursor:  cursorState{position: 11},
    74  			expectedCursor: cursorState{position: 2},
    75  		},
    76  		{
    77  			name:           "two lines, move up from shorter line with logical offset to longer line",
    78  			inputString:    "abcdefgh\nijk",
    79  			count:          1,
    80  			initialCursor:  cursorState{position: 11, logicalOffset: 2},
    81  			expectedCursor: cursorState{position: 4},
    82  		},
    83  		{
    84  			name:           "two lines, move up from longer line to shorter line",
    85  			inputString:    "abc\nefghijkl",
    86  			count:          1,
    87  			initialCursor:  cursorState{position: 9},
    88  			expectedCursor: cursorState{position: 2, logicalOffset: 3},
    89  		},
    90  		{
    91  			name:           "two lines, move up from longer line with logical offset to shorter line",
    92  			inputString:    "abc\nefghijkl",
    93  			count:          1,
    94  			initialCursor:  cursorState{position: 11, logicalOffset: 5},
    95  			expectedCursor: cursorState{position: 2, logicalOffset: 10},
    96  		},
    97  		{
    98  			name:           "two lines, move up with multi-char grapheme cluster",
    99  			inputString:    "abcde\u0301fgh\nijklmnopqrstuv",
   100  			count:          1,
   101  			initialCursor:  cursorState{position: 15},
   102  			expectedCursor: cursorState{position: 6},
   103  		},
   104  		{
   105  			name:           "three lines, move up from longer line to empty line",
   106  			inputString:    "abcd\n\nefghijkl",
   107  			count:          1,
   108  			initialCursor:  cursorState{position: 8},
   109  			expectedCursor: cursorState{position: 5, logicalOffset: 2},
   110  		},
   111  		{
   112  			name:           "move up multiple lines",
   113  			inputString:    "abcd\nefgh\nijkl",
   114  			count:          2,
   115  			initialCursor:  cursorState{position: 12},
   116  			expectedCursor: cursorState{position: 2},
   117  		},
   118  		{
   119  			name:           "move up to tab",
   120  			inputString:    "abcd\ne\tefg\nhijkl",
   121  			count:          1,
   122  			initialCursor:  cursorState{position: 13},
   123  			expectedCursor: cursorState{position: 6, logicalOffset: 1},
   124  		},
   125  		{
   126  			name:           "move up from tab",
   127  			inputString:    "abcd\ne\tefg\nhijkl",
   128  			count:          1,
   129  			initialCursor:  cursorState{position: 6, logicalOffset: 2},
   130  			expectedCursor: cursorState{position: 3},
   131  		},
   132  	}
   133  
   134  	for _, tc := range testCases {
   135  		t.Run(tc.name, func(t *testing.T) {
   136  			textTree, err := text.NewTreeFromString(tc.inputString)
   137  			require.NoError(t, err)
   138  			state := NewEditorState(100, 100, nil, nil)
   139  			state.documentBuffer.textTree = textTree
   140  			state.documentBuffer.cursor = tc.initialCursor
   141  			state.documentBuffer.tabSize = 4
   142  			MoveCursorToLineAbove(state, tc.count)
   143  			assert.Equal(t, tc.expectedCursor, state.documentBuffer.cursor)
   144  		})
   145  	}
   146  }
   147  
   148  func TestMoveCursorToLineBelow(t *testing.T) {
   149  	testCases := []struct {
   150  		name           string
   151  		inputString    string
   152  		count          uint64
   153  		initialCursor  cursorState
   154  		expectedCursor cursorState
   155  	}{
   156  		{
   157  			name:           "empty string, move down one line",
   158  			inputString:    "",
   159  			count:          1,
   160  			initialCursor:  cursorState{position: 0},
   161  			expectedCursor: cursorState{position: 0},
   162  		},
   163  		{
   164  			name:           "single line, move down one line",
   165  			inputString:    "abcdefgh",
   166  			count:          1,
   167  			initialCursor:  cursorState{position: 3},
   168  			expectedCursor: cursorState{position: 3},
   169  		},
   170  		{
   171  			name:           "single line, move down one line with logical offset",
   172  			inputString:    "abcdefgh",
   173  			count:          1,
   174  			initialCursor:  cursorState{position: 7, logicalOffset: 4},
   175  			expectedCursor: cursorState{position: 7, logicalOffset: 4},
   176  		},
   177  		{
   178  			name:           "two lines, move down one line at start of line",
   179  			inputString:    "abcdefgh\nijklm\nopqrs",
   180  			count:          1,
   181  			initialCursor:  cursorState{position: 9},
   182  			expectedCursor: cursorState{position: 15},
   183  		},
   184  		{
   185  			name:           "two lines, move down at same offset",
   186  			inputString:    "abcdefgh\nijklmnop",
   187  			count:          1,
   188  			initialCursor:  cursorState{position: 2},
   189  			expectedCursor: cursorState{position: 11},
   190  		},
   191  		{
   192  			name:           "two lines, move down from shorter line to longer line",
   193  			inputString:    "abc\nefghijkl",
   194  			count:          1,
   195  			initialCursor:  cursorState{position: 2},
   196  			expectedCursor: cursorState{position: 6},
   197  		},
   198  		{
   199  			name:           "two lines, move down from shorter line with logical offset to longer line",
   200  			inputString:    "abc\nefghijkl",
   201  			count:          1,
   202  			initialCursor:  cursorState{position: 2, logicalOffset: 3},
   203  			expectedCursor: cursorState{position: 9},
   204  		},
   205  		{
   206  			name:           "two lines, move down from longer line to shorter line",
   207  			inputString:    "abcdefgh\nijkl",
   208  			count:          1,
   209  			initialCursor:  cursorState{position: 7},
   210  			expectedCursor: cursorState{position: 12, logicalOffset: 4},
   211  		},
   212  		{
   213  			name:           "two lines, move down from longer line with logical offset to shorter line",
   214  			inputString:    "abcdefgh\nijkl",
   215  			count:          1,
   216  			initialCursor:  cursorState{position: 7, logicalOffset: 5},
   217  			expectedCursor: cursorState{position: 12, logicalOffset: 9},
   218  		},
   219  		{
   220  			name:           "two lines, move down with multi-char grapheme cluster",
   221  			inputString:    "abcdefgh\nijklmno\u0301pqrstuv",
   222  			count:          1,
   223  			initialCursor:  cursorState{position: 7},
   224  			expectedCursor: cursorState{position: 17},
   225  		},
   226  		{
   227  			name:           "three lines, move down from longer line to empty line",
   228  			inputString:    "abcdefgh\n\nijkl",
   229  			count:          1,
   230  			initialCursor:  cursorState{position: 2},
   231  			expectedCursor: cursorState{position: 9, logicalOffset: 2},
   232  		},
   233  		{
   234  			name:           "move down multiple lines",
   235  			inputString:    "abcd\nefgh\nijkl",
   236  			count:          2,
   237  			initialCursor:  cursorState{position: 2},
   238  			expectedCursor: cursorState{position: 12},
   239  		},
   240  		{
   241  			name:           "move down past newline at end of text",
   242  			inputString:    "abcd\nefgh\nijkl\n",
   243  			count:          1,
   244  			initialCursor:  cursorState{position: 12},
   245  			expectedCursor: cursorState{position: 15, logicalOffset: 2},
   246  		},
   247  		{
   248  			name:           "move down past single newline",
   249  			inputString:    "\n",
   250  			count:          1,
   251  			initialCursor:  cursorState{position: 0},
   252  			expectedCursor: cursorState{position: 1},
   253  		},
   254  		{
   255  			name:           "move down to tab",
   256  			inputString:    "abcd\ne\tefg\nhijkl",
   257  			count:          1,
   258  			initialCursor:  cursorState{position: 3},
   259  			expectedCursor: cursorState{position: 6, logicalOffset: 2},
   260  		},
   261  		{
   262  			name:           "move down from tab",
   263  			inputString:    "abcd\ne\tefg\nhijkl",
   264  			count:          1,
   265  			initialCursor:  cursorState{position: 6, logicalOffset: 1},
   266  			expectedCursor: cursorState{position: 13},
   267  		},
   268  	}
   269  
   270  	for _, tc := range testCases {
   271  		t.Run(tc.name, func(t *testing.T) {
   272  			textTree, err := text.NewTreeFromString(tc.inputString)
   273  			require.NoError(t, err)
   274  			state := NewEditorState(100, 100, nil, nil)
   275  			state.documentBuffer.textTree = textTree
   276  			state.documentBuffer.cursor = tc.initialCursor
   277  			state.documentBuffer.tabSize = 4
   278  			MoveCursorToLineBelow(state, tc.count)
   279  			assert.Equal(t, tc.expectedCursor, state.documentBuffer.cursor)
   280  		})
   281  	}
   282  }
   283  
   284  func TestMoveCursorToStartOfSelection(t *testing.T) {
   285  	testCases := []struct {
   286  		name              string
   287  		inputString       string
   288  		selectionMode     selection.Mode
   289  		selectionStartPos uint64
   290  		selectionEndPos   uint64
   291  		expectedCursor    cursorState
   292  	}{
   293  		{
   294  			name:              "empty",
   295  			inputString:       "",
   296  			selectionMode:     selection.ModeChar,
   297  			selectionStartPos: 0,
   298  			selectionEndPos:   0,
   299  			expectedCursor:    cursorState{position: 0},
   300  		},
   301  		{
   302  			name:              "no selection",
   303  			inputString:       "abcdefh",
   304  			selectionMode:     selection.ModeNone,
   305  			selectionStartPos: 1,
   306  			selectionEndPos:   3,
   307  			expectedCursor:    cursorState{position: 3},
   308  		},
   309  		{
   310  			name:              "charwise, select forward",
   311  			inputString:       "abcdefgh",
   312  			selectionMode:     selection.ModeChar,
   313  			selectionStartPos: 1,
   314  			selectionEndPos:   3,
   315  			expectedCursor:    cursorState{position: 1},
   316  		},
   317  		{
   318  			name:              "charwise, select backward",
   319  			inputString:       "abcdefgh",
   320  			selectionMode:     selection.ModeChar,
   321  			selectionStartPos: 3,
   322  			selectionEndPos:   1,
   323  			expectedCursor:    cursorState{position: 1},
   324  		},
   325  		{
   326  			name:              "linewise select forward",
   327  			inputString:       "abcd\nef\ngh\nijkl",
   328  			selectionMode:     selection.ModeLine,
   329  			selectionStartPos: 6,
   330  			selectionEndPos:   9,
   331  			expectedCursor:    cursorState{position: 5},
   332  		},
   333  		{
   334  			name:              "linewise select backward",
   335  			inputString:       "abcd\nef\ngh\nijkl",
   336  			selectionMode:     selection.ModeLine,
   337  			selectionStartPos: 9,
   338  			selectionEndPos:   6,
   339  			expectedCursor:    cursorState{position: 5},
   340  		},
   341  	}
   342  
   343  	for _, tc := range testCases {
   344  		t.Run(tc.name, func(t *testing.T) {
   345  			textTree, err := text.NewTreeFromString(tc.inputString)
   346  			require.NoError(t, err)
   347  			state := NewEditorState(100, 100, nil, nil)
   348  			state.documentBuffer.textTree = textTree
   349  			state.documentBuffer.selector.Start(tc.selectionMode, tc.selectionStartPos)
   350  			state.documentBuffer.cursor = cursorState{position: tc.selectionEndPos}
   351  			MoveCursorToStartOfSelection(state)
   352  			assert.Equal(t, tc.expectedCursor, state.documentBuffer.cursor)
   353  		})
   354  	}
   355  }
   356  
   357  func TestSelectRange(t *testing.T) {
   358  	textTree, err := text.NewTreeFromString("abc def ghi")
   359  	require.NoError(t, err)
   360  	state := NewEditorState(100, 100, nil, nil)
   361  	state.documentBuffer.textTree = textTree
   362  	state.documentBuffer.selector.Start(selection.ModeLine, 0)
   363  	state.documentBuffer.cursor = cursorState{position: 3}
   364  
   365  	SelectRange(state, func(LocatorParams) (uint64, uint64) {
   366  		return 5, 7
   367  	})
   368  	assert.Equal(t, selection.ModeChar, state.documentBuffer.SelectionMode())
   369  	assert.Equal(t, selection.Region{StartPos: 5, EndPos: 7}, state.documentBuffer.SelectedRegion())
   370  	assert.Equal(t, cursorState{position: 6}, state.documentBuffer.cursor)
   371  }