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

     1  package state
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/aretext/aretext/config"
    14  	"github.com/aretext/aretext/syntax"
    15  )
    16  
    17  func createTestFile(t *testing.T, contents string) (path string, cleanup func()) {
    18  	f, err := os.CreateTemp(t.TempDir(), "aretext-")
    19  	require.NoError(t, err)
    20  	defer f.Close()
    21  
    22  	_, err = io.WriteString(f, contents)
    23  	require.NoError(t, err)
    24  
    25  	cleanup = func() { os.Remove(f.Name()) }
    26  	return f.Name(), cleanup
    27  }
    28  
    29  func startOfDocLocator(LocatorParams) uint64 { return 0 }
    30  
    31  func TestLoadDocumentShowStatus(t *testing.T) {
    32  	// Start with an empty document.
    33  	state := NewEditorState(100, 100, nil, nil)
    34  	defer state.fileWatcher.Stop()
    35  	assert.Equal(t, "", state.documentBuffer.textTree.String())
    36  	assert.Equal(t, "", state.FileWatcher().Path())
    37  
    38  	// Load a document.
    39  	path, cleanup := createTestFile(t, "abcd")
    40  	LoadDocument(state, path, true, startOfDocLocator)
    41  
    42  	// Expect that the text and watcher are installed.
    43  	assert.Equal(t, "abcd", state.documentBuffer.textTree.String())
    44  	assert.Equal(t, path, state.FileWatcher().Path())
    45  
    46  	// Expect success message.
    47  	assert.Contains(t, state.statusMsg.Text, "Opened")
    48  	assert.Equal(t, StatusMsgStyleSuccess, state.statusMsg.Style)
    49  
    50  	// Delete the test file.
    51  	cleanup()
    52  
    53  	// Load a non-existent path, expect error msg.
    54  	LoadDocument(state, path, true, startOfDocLocator)
    55  	defer state.fileWatcher.Stop()
    56  	assert.Contains(t, state.statusMsg.Text, "Could not open")
    57  	assert.Equal(t, StatusMsgStyleError, state.statusMsg.Style)
    58  }
    59  
    60  func TestLoadDocumentSameFile(t *testing.T) {
    61  	// Load the initial document.
    62  	path, cleanup := createTestFile(t, "abcd\nefghi\njklmnop\nqrst")
    63  	defer cleanup()
    64  	state := NewEditorState(5, 3, nil, nil)
    65  	defer state.fileWatcher.Stop()
    66  	LoadDocument(state, path, true, startOfDocLocator)
    67  	state.documentBuffer.cursor.position = 22
    68  
    69  	// Scroll to cursor at end of document.
    70  	ScrollViewToCursor(state)
    71  	assert.Equal(t, uint64(16), state.documentBuffer.view.textOrigin)
    72  
    73  	// Update the file with shorter text and reload.
    74  	err := os.WriteFile(path, []byte("ab"), 0644)
    75  	require.NoError(t, err)
    76  	ReloadDocument(state)
    77  	defer state.fileWatcher.Stop()
    78  
    79  	// Expect that the cursor moved back to the end of the text,
    80  	// and the view scrolled to make the cursor visible.
    81  	assert.Equal(t, "ab", state.documentBuffer.textTree.String())
    82  	assert.Equal(t, uint64(1), state.documentBuffer.cursor.position)
    83  	assert.Equal(t, uint64(0), state.documentBuffer.view.textOrigin)
    84  }
    85  
    86  func TestLoadDocumentDifferentFile(t *testing.T) {
    87  	// Load the initial document.
    88  	path, cleanup := createTestFile(t, "abcd\nefghi\njklmnop\nqrst")
    89  	defer cleanup()
    90  	state := NewEditorState(5, 3, nil, nil)
    91  	defer state.fileWatcher.Stop()
    92  	LoadDocument(state, path, true, startOfDocLocator)
    93  	state.documentBuffer.cursor.position = 22
    94  
    95  	// Scroll to cursor at end of document.
    96  	ScrollViewToCursor(state)
    97  	assert.Equal(t, uint64(16), state.documentBuffer.view.textOrigin)
    98  
    99  	// Set the syntax.
   100  	SetSyntax(state, syntax.LanguageJson)
   101  	assert.Equal(t, syntax.LanguageJson, state.documentBuffer.syntaxLanguage)
   102  
   103  	// Load a new document with a shorter text.
   104  	path2, cleanup2 := createTestFile(t, "ab")
   105  	defer cleanup2()
   106  	LoadDocument(state, path2, true, startOfDocLocator)
   107  	defer state.fileWatcher.Stop()
   108  
   109  	// Expect that the cursor, view, and syntax are reset.
   110  	assert.Equal(t, "ab", state.documentBuffer.textTree.String())
   111  	assert.Equal(t, uint64(0), state.documentBuffer.cursor.position)
   112  	assert.Equal(t, uint64(0), state.documentBuffer.view.textOrigin)
   113  	assert.Equal(t, syntax.LanguagePlaintext, state.documentBuffer.syntaxLanguage)
   114  }
   115  
   116  func TestLoadPrevDocument(t *testing.T) {
   117  	// Load the initial document.
   118  	path, cleanup := createTestFile(t, "abcd\nefghi\njklmnop\nqrst")
   119  	defer cleanup()
   120  	state := NewEditorState(5, 3, nil, nil)
   121  	defer state.fileWatcher.Stop()
   122  	LoadDocument(state, path, true, startOfDocLocator)
   123  	MoveCursor(state, func(LocatorParams) uint64 {
   124  		return 7
   125  	})
   126  
   127  	// Load another document.
   128  	path2, cleanup2 := createTestFile(t, "xyz")
   129  	defer cleanup2()
   130  	LoadDocument(state, path2, true, startOfDocLocator)
   131  	defer state.fileWatcher.Stop()
   132  	assert.Equal(t, "xyz", state.documentBuffer.textTree.String())
   133  
   134  	// Return to the previous document.
   135  	LoadPrevDocument(state)
   136  	assert.Equal(t, "abcd\nefghi\njklmnop\nqrst", state.documentBuffer.textTree.String())
   137  	assert.Equal(t, path, state.fileWatcher.Path())
   138  	assert.Equal(t, uint64(7), state.documentBuffer.cursor.position)
   139  }
   140  
   141  func TestLoadNextDocument(t *testing.T) {
   142  	// Load the initial document.
   143  	path, cleanup := createTestFile(t, "abcd\nefghi\njklmnop\nqrst")
   144  	defer cleanup()
   145  	state := NewEditorState(5, 3, nil, nil)
   146  	defer state.fileWatcher.Stop()
   147  	LoadDocument(state, path, true, startOfDocLocator)
   148  	MoveCursor(state, func(LocatorParams) uint64 { return 7 })
   149  
   150  	// Load another document.
   151  	path2, cleanup2 := createTestFile(t, "qrs\ntuv\nwxyz")
   152  	defer cleanup2()
   153  	LoadDocument(state, path2, true, startOfDocLocator)
   154  	defer state.fileWatcher.Stop()
   155  	assert.Equal(t, path2, state.fileWatcher.Path())
   156  	MoveCursor(state, func(LocatorParams) uint64 { return 5 })
   157  
   158  	// Return to the previous document.
   159  	LoadPrevDocument(state)
   160  	assert.Equal(t, path, state.fileWatcher.Path())
   161  
   162  	// Forward to the next document.
   163  	LoadNextDocument(state)
   164  	assert.Equal(t, path2, state.fileWatcher.Path())
   165  	assert.Equal(t, "qrs\ntuv\nwxyz", state.documentBuffer.textTree.String())
   166  	assert.Equal(t, uint64(5), state.documentBuffer.cursor.position)
   167  }
   168  
   169  func TestLoadDocumentIncrementLoadCount(t *testing.T) {
   170  	// Start with an empty document.
   171  	state := NewEditorState(100, 100, nil, nil)
   172  	defer state.fileWatcher.Stop()
   173  	assert.Equal(t, state.DocumentLoadCount(), 0)
   174  
   175  	// Load a document.
   176  	path, cleanup := createTestFile(t, "abcd")
   177  	LoadDocument(state, path, true, startOfDocLocator)
   178  	defer cleanup()
   179  
   180  	// Expect that the load count was bumped.
   181  	assert.Equal(t, state.DocumentLoadCount(), 1)
   182  }
   183  
   184  func TestReloadDocumentAlignCursorAndScroll(t *testing.T) {
   185  	// Load the initial document.
   186  	initialText := "abcd\nefghi\njklmnop\nqrst"
   187  	path, cleanup := createTestFile(t, initialText)
   188  	defer cleanup()
   189  	state := NewEditorState(5, 3, nil, nil)
   190  	defer state.fileWatcher.Stop()
   191  	LoadDocument(state, path, true, startOfDocLocator)
   192  	state.documentBuffer.cursor.position = 14
   193  
   194  	// Scroll to cursor at end of document.
   195  	ScrollViewToCursor(state)
   196  	assert.Equal(t, uint64(5), state.documentBuffer.view.textOrigin)
   197  
   198  	// Add some lines to the beginning of the document.
   199  	insertedText := "123\n456\n789\nqrs\ntuv\nwx\nyz\n"
   200  	err := os.WriteFile(path, []byte(insertedText+initialText), 0644)
   201  	require.NoError(t, err)
   202  
   203  	// Reload the document.
   204  	ReloadDocument(state)
   205  	defer state.fileWatcher.Stop()
   206  
   207  	// Expect that the cursor and scroll position moved to the
   208  	// equivalent line in the new document.
   209  	assert.Equal(t, insertedText+initialText, state.documentBuffer.textTree.String())
   210  	assert.Equal(t, uint64(40), state.documentBuffer.cursor.position)
   211  	assert.Equal(t, uint64(31), state.documentBuffer.view.textOrigin)
   212  }
   213  
   214  func TestReloadDocumentWithMenuOpen(t *testing.T) {
   215  	// Load the initial document.
   216  	path, cleanup := createTestFile(t, "abcd\nefghi\njklmnop\nqrst")
   217  	defer cleanup()
   218  	state := NewEditorState(5, 3, nil, nil)
   219  	defer state.fileWatcher.Stop()
   220  	LoadDocument(state, path, true, startOfDocLocator)
   221  
   222  	// Open the command menu
   223  	ShowMenu(state, MenuStyleCommand, nil)
   224  	assert.Equal(t, state.InputMode(), InputModeMenu)
   225  
   226  	// Update the file with shorter text and reload.
   227  	err := os.WriteFile(path, []byte("ab"), 0644)
   228  	require.NoError(t, err)
   229  	ReloadDocument(state)
   230  	defer state.fileWatcher.Stop()
   231  
   232  	// Expect that the input mode is normal and the menu is hidden.
   233  	assert.Equal(t, "ab", state.documentBuffer.textTree.String())
   234  	assert.Equal(t, InputModeNormal, state.InputMode())
   235  }
   236  
   237  func TestReloadDocumentPreserveSearchQueryAndDirection(t *testing.T) {
   238  	testCases := []struct {
   239  		name           string
   240  		direction      SearchDirection
   241  		completeSearch bool
   242  	}{
   243  		{
   244  			name:           "search forward, complete search",
   245  			direction:      SearchDirectionForward,
   246  			completeSearch: true,
   247  		},
   248  		{
   249  			name:           "search backward, complete search",
   250  			direction:      SearchDirectionBackward,
   251  			completeSearch: true,
   252  		},
   253  		{
   254  			name:           "search forward, incomplete search",
   255  			direction:      SearchDirectionForward,
   256  			completeSearch: false,
   257  		},
   258  		{
   259  			name:           "search backward, incomplete search",
   260  			direction:      SearchDirectionBackward,
   261  			completeSearch: false,
   262  		},
   263  	}
   264  
   265  	for _, tc := range testCases {
   266  		t.Run(tc.name, func(t *testing.T) {
   267  			// Load the initial document.
   268  			path, cleanup := createTestFile(t, "abcd\nefghi\njklmnop\nqrst")
   269  			defer cleanup()
   270  			state := NewEditorState(5, 3, nil, nil)
   271  			defer state.fileWatcher.Stop()
   272  			LoadDocument(state, path, true, startOfDocLocator)
   273  
   274  			// Text search.
   275  			StartSearch(state, tc.direction, SearchCompleteMoveCursorToMatch)
   276  			AppendRuneToSearchQuery(state, 'e')
   277  			AppendRuneToSearchQuery(state, 'f')
   278  			AppendRuneToSearchQuery(state, 'g')
   279  			if tc.completeSearch {
   280  				CompleteSearch(state, true)
   281  			}
   282  
   283  			// Update the file with shorter text and reload.
   284  			err := os.WriteFile(path, []byte("abcefghijk"), 0644)
   285  			require.NoError(t, err)
   286  			ReloadDocument(state)
   287  			defer state.fileWatcher.Stop()
   288  
   289  			// Expect we're in normal mode after reload.
   290  			assert.Equal(t, InputModeNormal, state.InputMode())
   291  
   292  			// Expect that the search query and direction are preserved.
   293  			expectedSearch := searchState{query: "efg", direction: tc.direction}
   294  			if tc.completeSearch {
   295  				expectedSearch.history = []string{"efg"}
   296  			}
   297  			assert.Equal(t, expectedSearch, state.documentBuffer.search)
   298  		})
   299  	}
   300  }
   301  
   302  func TestSaveDocument(t *testing.T) {
   303  	// Start with an empty document.
   304  	state := NewEditorState(100, 100, nil, nil)
   305  	defer state.fileWatcher.Stop()
   306  
   307  	// Load an existing document.
   308  	path, cleanup := createTestFile(t, "")
   309  	defer cleanup()
   310  	LoadDocument(state, path, true, startOfDocLocator)
   311  
   312  	// Modify and save the document
   313  	InsertRune(state, 'x')
   314  	SaveDocument(state)
   315  
   316  	// Expect a success message.
   317  	assert.Contains(t, state.statusMsg.Text, "Saved")
   318  	assert.Equal(t, StatusMsgStyleSuccess, state.statusMsg.Style)
   319  
   320  	// Check that the changes were persisted
   321  	contents, err := os.ReadFile(path)
   322  	require.NoError(t, err)
   323  	assert.Equal(t, "x\n", string(contents))
   324  }
   325  
   326  func TestSaveDocumentIfUnsavedChanges(t *testing.T) {
   327  	// Start with an empty document.
   328  	state := NewEditorState(100, 100, nil, nil)
   329  	defer state.fileWatcher.Stop()
   330  	path := filepath.Join(t.TempDir(), "test-save-document-if-unsaved-changes.txt")
   331  	LoadDocument(state, path, false, func(LocatorParams) uint64 { return 0 })
   332  
   333  	// Save the document. The file should be created even though the document is empty.
   334  	SaveDocumentIfUnsavedChanges(state)
   335  	_, err := os.Stat(path)
   336  	require.NoError(t, err)
   337  
   338  	// Change the document on disk so we can detect if the file changes on next save.
   339  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0644)
   340  	require.NoError(t, err)
   341  	defer f.Close()
   342  	_, err = io.WriteString(f, "abcdefg")
   343  	require.NoError(t, err)
   344  
   345  	// Save again, but expect that the file is NOT saved since there are no unsaved changes.
   346  	SaveDocumentIfUnsavedChanges(state)
   347  	contents, err := os.ReadFile(path)
   348  	require.NoError(t, err)
   349  	assert.Equal(t, "abcdefg", string(contents))
   350  
   351  	// Modify and save the document, then check that the file was changed.
   352  	BeginUndoEntry(state)
   353  	InsertRune(state, 'x')
   354  	CommitUndoEntry(state)
   355  	SaveDocumentIfUnsavedChanges(state)
   356  	contents, err = os.ReadFile(path)
   357  	require.NoError(t, err)
   358  	assert.Equal(t, "x\n", string(contents))
   359  }
   360  
   361  func TestAbortIfFileChanged(t *testing.T) {
   362  	testCases := []struct {
   363  		name        string
   364  		didChange   bool
   365  		expectAbort bool
   366  	}{
   367  		{
   368  			name:        "no changes should commit",
   369  			didChange:   false,
   370  			expectAbort: false,
   371  		},
   372  		{
   373  			name:        "changes should abort",
   374  			didChange:   true,
   375  			expectAbort: true,
   376  		},
   377  	}
   378  
   379  	for _, tc := range testCases {
   380  		t.Run(tc.name, func(t *testing.T) {
   381  			// Load the initial document.
   382  			path, cleanup := createTestFile(t, "")
   383  			defer cleanup()
   384  			state := NewEditorState(100, 100, nil, nil)
   385  			defer state.fileWatcher.Stop()
   386  			LoadDocument(state, path, true, startOfDocLocator)
   387  
   388  			// Modify the file.
   389  			if tc.didChange {
   390  				f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
   391  				require.NoError(t, err)
   392  				defer f.Close()
   393  				_, err = io.WriteString(f, "test")
   394  				require.NoError(t, err)
   395  			}
   396  
   397  			// Attempt an operation, but abort if the file changed.
   398  			AbortIfFileChanged(state, func(state *EditorState) {
   399  				SetStatusMsg(state, StatusMsg{
   400  					Style: StatusMsgStyleSuccess,
   401  					Text:  "Operation executed",
   402  				})
   403  			})
   404  
   405  			if tc.expectAbort {
   406  				assert.Equal(t, StatusMsgStyleError, state.statusMsg.Style)
   407  				assert.Contains(t, state.statusMsg.Text, "changed since last save")
   408  			} else {
   409  				assert.Equal(t, StatusMsgStyleSuccess, state.statusMsg.Style)
   410  				assert.Equal(t, "Operation executed", state.statusMsg.Text)
   411  			}
   412  		})
   413  	}
   414  }
   415  
   416  func TestAbortIfFileChangedNewFile(t *testing.T) {
   417  	dir := t.TempDir()
   418  
   419  	path := filepath.Join(dir, "aretext-does-not-exist")
   420  	state := NewEditorState(100, 100, nil, nil)
   421  	defer state.fileWatcher.Stop()
   422  	LoadDocument(state, path, false, startOfDocLocator)
   423  
   424  	// File doesn't exist on disk, so the operation should succeed.
   425  	AbortIfFileChanged(state, func(state *EditorState) {
   426  		SetStatusMsg(state, StatusMsg{
   427  			Style: StatusMsgStyleSuccess,
   428  			Text:  "Operation executed",
   429  		})
   430  	})
   431  	assert.Equal(t, StatusMsgStyleSuccess, state.statusMsg.Style)
   432  	assert.Equal(t, "Operation executed", state.statusMsg.Text)
   433  
   434  	// Create the file on disk.
   435  	f, err := os.Create(path)
   436  	require.NoError(t, err)
   437  	defer f.Close()
   438  
   439  	_, err = io.WriteString(f, "abcd")
   440  	require.NoError(t, err)
   441  
   442  	// Now the operation should abort.
   443  	AbortIfFileChanged(state, func(state *EditorState) {
   444  		SetStatusMsg(state, StatusMsg{
   445  			Style: StatusMsgStyleSuccess,
   446  			Text:  "Operation executed",
   447  		})
   448  	})
   449  	assert.Equal(t, StatusMsgStyleError, state.statusMsg.Style)
   450  	assert.Contains(t, state.statusMsg.Text, "changed since last save")
   451  }
   452  
   453  func TestAbortIfFileChangedExistingFileDeleted(t *testing.T) {
   454  	// Load the initial document.
   455  	path, cleanup := createTestFile(t, "abc")
   456  	state := NewEditorState(100, 100, nil, nil)
   457  	defer state.fileWatcher.Stop()
   458  	LoadDocument(state, path, true, startOfDocLocator)
   459  
   460  	// Delete the file.
   461  	cleanup()
   462  
   463  	// The operation should fail, since the file was deleted.
   464  	AbortIfFileChanged(state, func(state *EditorState) {
   465  		SetStatusMsg(state, StatusMsg{
   466  			Style: StatusMsgStyleSuccess,
   467  			Text:  "Operation executed",
   468  		})
   469  	})
   470  	assert.Equal(t, StatusMsgStyleError, state.statusMsg.Style)
   471  	assert.Contains(t, state.statusMsg.Text, "moved or deleted")
   472  }
   473  
   474  func TestDeduplicateCustomMenuItems(t *testing.T) {
   475  	// Configure custom menu items with duplicate names.
   476  	configRuleSet := config.RuleSet{
   477  		{
   478  			Name:    "customMenuCommands",
   479  			Pattern: "**",
   480  			Config: map[string]any{
   481  				"menuCommands": []any{
   482  					map[string]any{
   483  						"name":     "foo",
   484  						"shellCmd": "echo 'foo'",
   485  						"mode":     "insert",
   486  					},
   487  					map[string]any{
   488  						"name":     "bar",
   489  						"shellCmd": "echo 'bar'",
   490  						"mode":     "insert",
   491  					},
   492  					map[string]any{
   493  						"name":     "foo", // duplicate
   494  						"shellCmd": "echo 'foo2'",
   495  						"mode":     "insert",
   496  					},
   497  				},
   498  			},
   499  		},
   500  	}
   501  
   502  	// Load the document.
   503  	path, cleanup := createTestFile(t, "")
   504  	state := NewEditorState(100, 100, configRuleSet, nil)
   505  	defer state.fileWatcher.Stop()
   506  	LoadDocument(state, path, true, startOfDocLocator)
   507  	defer cleanup()
   508  
   509  	// Show the menu and search for 'f', which should match all custom items.
   510  	ShowMenu(state, MenuStyleCommand, nil)
   511  	AppendRuneToMenuSearch(state, 'f')
   512  
   513  	// Expect that the search results include only two items,
   514  	// since "foo" was deduplicated.
   515  	results, selectedIdx := state.Menu().SearchResults()
   516  	assert.Equal(t, len(results), 2)
   517  	assert.Equal(t, selectedIdx, 0)
   518  	assert.Equal(t, results[0].Name, "foo")
   519  	assert.Equal(t, results[1].Name, "bar")
   520  
   521  	// Execute the "foo" item and wait for the shell cmd to complete.
   522  	ExecuteSelectedMenuItem(state)
   523  	select {
   524  	case action := <-state.TaskResultChan():
   525  		action(state)
   526  	case <-time.After(5 * time.Second):
   527  		require.Fail(t, "Timed out")
   528  	}
   529  
   530  	// Check that the second "foo" command was invoked, which should
   531  	// have inserted "foo2" into the document.
   532  	text := state.DocumentBuffer().TextTree().String()
   533  	assert.Equal(t, text, "foo2\n")
   534  }
   535  
   536  func TestNewDocument(t *testing.T) {
   537  	tmpDir := t.TempDir()
   538  	path := filepath.Join(tmpDir, "test.txt")
   539  
   540  	state := NewEditorState(100, 100, nil, nil)
   541  	defer state.fileWatcher.Stop()
   542  	err := NewDocument(state, path)
   543  	require.NoError(t, err)
   544  
   545  	assert.Equal(t, path, state.FileWatcher().Path())
   546  }
   547  
   548  func TestNewDocumentFileAlreadyExists(t *testing.T) {
   549  	path, cleanup := createTestFile(t, "abcd")
   550  	defer cleanup()
   551  
   552  	state := NewEditorState(100, 100, nil, nil)
   553  	defer state.fileWatcher.Stop()
   554  	err := NewDocument(state, path)
   555  	assert.ErrorContains(t, err, "File already exists")
   556  }
   557  
   558  func TestRenameDocument(t *testing.T) {
   559  	tmpDir := t.TempDir()
   560  	path := filepath.Join(tmpDir, "before.txt")
   561  	_, err := os.Create(path)
   562  	require.NoError(t, err)
   563  
   564  	state := NewEditorState(100, 100, nil, nil)
   565  	defer state.fileWatcher.Stop()
   566  	LoadDocument(state, path, true, startOfDocLocator)
   567  
   568  	newPath := filepath.Join(filepath.Dir(path), "renamed.txt")
   569  	err = RenameDocument(state, newPath)
   570  	require.NoError(t, err)
   571  	assert.Equal(t, newPath, state.FileWatcher().Path())
   572  }
   573  
   574  func TestRenameDocumentSrcFileNotSaved(t *testing.T) {
   575  	tmpDir := t.TempDir()
   576  	path := filepath.Join(tmpDir, "test.txt")
   577  
   578  	state := NewEditorState(100, 100, nil, nil)
   579  	defer state.fileWatcher.Stop()
   580  	LoadDocument(state, path, true, startOfDocLocator)
   581  
   582  	newPath := filepath.Join(filepath.Dir(path), "renamed.txt")
   583  	err := RenameDocument(state, newPath)
   584  	require.NoError(t, err)
   585  	assert.Equal(t, newPath, state.FileWatcher().Path())
   586  }
   587  
   588  func TestRenameDocumentDestFileAlreadyExists(t *testing.T) {
   589  	tmpDir := t.TempDir()
   590  	path := filepath.Join(tmpDir, "test.txt")
   591  
   592  	state := NewEditorState(100, 100, nil, nil)
   593  	defer state.fileWatcher.Stop()
   594  	LoadDocument(state, path, true, startOfDocLocator)
   595  
   596  	newPath := filepath.Join(filepath.Dir(path), "renamed.txt")
   597  	_, err := os.Create(newPath)
   598  	require.NoError(t, err)
   599  
   600  	err = RenameDocument(state, newPath)
   601  	assert.ErrorContains(t, err, "File already exists")
   602  }