github.com/aretext/aretext@v1.3.0/locate/word_test.go (about)

     1  package locate
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/aretext/aretext/text"
    11  	"github.com/aretext/aretext/text/segment"
    12  )
    13  
    14  func TestNextWordStart(t *testing.T) {
    15  	testCases := []struct {
    16  		name                string
    17  		inputString         string
    18  		pos                 uint64
    19  		count               uint64
    20  		withPunct           bool
    21  		stopAtEndOfLastLine bool
    22  		expectedPos         uint64
    23  	}{
    24  		{
    25  			name:        "empty",
    26  			inputString: "",
    27  			pos:         0,
    28  			count:       1,
    29  			expectedPos: 0,
    30  		},
    31  		{
    32  			name:        "next word from current word, same line",
    33  			inputString: "abc   defg   hij",
    34  			pos:         1,
    35  			count:       1,
    36  			expectedPos: 6,
    37  		},
    38  		{
    39  			name:        "next word from whitespace, same line",
    40  			inputString: "abc   defg   hij",
    41  			pos:         4,
    42  			count:       1,
    43  			expectedPos: 6,
    44  		},
    45  		{
    46  			name:        "next word from different line",
    47  			inputString: "abc\n   123",
    48  			pos:         1,
    49  			count:       1,
    50  			expectedPos: 7,
    51  		},
    52  		{
    53  			name:        "next word to empty line",
    54  			inputString: "abc\n\n   123",
    55  			pos:         1,
    56  			count:       1,
    57  			expectedPos: 4,
    58  		},
    59  		{
    60  			name:        "line ending with punctuation",
    61  			inputString: "abc.\n123",
    62  			pos:         3,
    63  			count:       1,
    64  			expectedPos: 5,
    65  		},
    66  		{
    67  			name:        "line ending with punctuation",
    68  			inputString: "abc.\n123",
    69  			pos:         3,
    70  			count:       1,
    71  			expectedPos: 5,
    72  			withPunct:   true,
    73  		},
    74  		{
    75  			name:        "punctuation before whitespace",
    76  			inputString: "-   foo",
    77  			pos:         0,
    78  			count:       1,
    79  			expectedPos: 4,
    80  		},
    81  		{
    82  			name:        "punctuation before whitespace",
    83  			inputString: "-   foo",
    84  			pos:         0,
    85  			count:       1,
    86  			expectedPos: 4,
    87  			withPunct:   true,
    88  		},
    89  		{
    90  			name:        "empty line to next word",
    91  			inputString: "abc\n\n   123",
    92  			pos:         4,
    93  			count:       1,
    94  			expectedPos: 8,
    95  		},
    96  		{
    97  			name:        "multiple empty lines",
    98  			inputString: "\n\n\n\n",
    99  			pos:         1,
   100  			count:       1,
   101  			expectedPos: 2,
   102  		},
   103  		{
   104  			name:        "non-punctuation to punctuation",
   105  			inputString: "abc/def/ghi",
   106  			pos:         1,
   107  			count:       1,
   108  			expectedPos: 3,
   109  		},
   110  		{
   111  			name:        "non-punctuation to punctuation",
   112  			inputString: "abc/def/ghi",
   113  			pos:         1,
   114  			count:       1,
   115  			expectedPos: 11,
   116  			withPunct:   true,
   117  		},
   118  		{
   119  			name:        "punctuation to non-punctuation",
   120  			inputString: "abc/def/ghi",
   121  			pos:         3,
   122  			count:       1,
   123  			expectedPos: 4,
   124  		},
   125  		{
   126  			name:        "punctuation to non-punctuation",
   127  			inputString: "abc/def/ghi",
   128  			pos:         3,
   129  			count:       1,
   130  			expectedPos: 11,
   131  			withPunct:   true,
   132  		},
   133  		{
   134  			name:        "repeated punctuation",
   135  			inputString: "abc////cde",
   136  			pos:         3,
   137  			count:       1,
   138  			expectedPos: 7,
   139  		},
   140  		{
   141  			name:        "repeated punctuation",
   142  			inputString: "abc////cde",
   143  			pos:         3,
   144  			count:       1,
   145  			expectedPos: 10,
   146  			withPunct:   true,
   147  		},
   148  		{
   149  			name:        "underscores treated as non-punctuation",
   150  			inputString: "abc_def ghi",
   151  			pos:         0,
   152  			count:       1,
   153  			expectedPos: 8,
   154  		},
   155  		{
   156  			name:        "underscores treated as non-punctuation",
   157  			inputString: "abc_def ghi",
   158  			pos:         0,
   159  			count:       1,
   160  			expectedPos: 8,
   161  			withPunct:   true,
   162  		},
   163  		{
   164  			name:        "last word in document",
   165  			inputString: "foo bar",
   166  			pos:         5,
   167  			count:       1,
   168  			expectedPos: 7,
   169  		},
   170  		{
   171  			name:        "count zero",
   172  			inputString: "lorem ipsum dolor sit amet",
   173  			pos:         2,
   174  			count:       0,
   175  			expectedPos: 2,
   176  		},
   177  		{
   178  			name:        "count three",
   179  			inputString: "lorem ipsum dolor sit amet",
   180  			pos:         2,
   181  			count:       3,
   182  			expectedPos: 18,
   183  		},
   184  		{
   185  			name:                "stop at end of last line",
   186  			inputString:         "foo bar\nbaz bat\n",
   187  			pos:                 4,
   188  			count:               1,
   189  			stopAtEndOfLastLine: true,
   190  			expectedPos:         7,
   191  		},
   192  		{
   193  			name:                "stop at end of last line, on empty line",
   194  			inputString:         "foo\n\nbaz bat\n",
   195  			pos:                 3,
   196  			count:               1,
   197  			stopAtEndOfLastLine: true,
   198  			expectedPos:         3,
   199  		},
   200  		{
   201  			name:                "stop at end of last line with count",
   202  			inputString:         "foo bar\nbaz bat\nlorem",
   203  			pos:                 0,
   204  			count:               4,
   205  			stopAtEndOfLastLine: true,
   206  			expectedPos:         15,
   207  		},
   208  	}
   209  
   210  	for _, tc := range testCases {
   211  		t.Run(tc.name, func(t *testing.T) {
   212  			textTree, err := text.NewTreeFromString(tc.inputString)
   213  			require.NoError(t, err)
   214  			actualPos := NextWordStart(textTree, tc.pos, tc.count, tc.withPunct, tc.stopAtEndOfLastLine)
   215  			assert.Equal(t, tc.expectedPos, actualPos)
   216  		})
   217  	}
   218  }
   219  
   220  func TestNextWordEnd(t *testing.T) {
   221  	testCases := []struct {
   222  		name        string
   223  		inputString string
   224  		pos         uint64
   225  		count       uint64
   226  		expectedPos uint64
   227  		withPunct   bool
   228  	}{
   229  		{
   230  			name:        "empty",
   231  			inputString: "",
   232  			pos:         0,
   233  			count:       1,
   234  			expectedPos: 0,
   235  		},
   236  		{
   237  			name:        "end of word from start of current word",
   238  			inputString: "abc   defg   hij",
   239  			pos:         6,
   240  			count:       1,
   241  			expectedPos: 9,
   242  		},
   243  		{
   244  			name:        "end of word from middle of current word",
   245  			inputString: "abc   defg   hij",
   246  			pos:         7,
   247  			count:       1,
   248  			expectedPos: 9,
   249  		},
   250  		{
   251  			name:        "next word from end of current word",
   252  			inputString: "abc   defg   hij",
   253  			pos:         2,
   254  			count:       1,
   255  			expectedPos: 9,
   256  		},
   257  		{
   258  			name:        "next word from whitespace",
   259  			inputString: "abc   defg   hij",
   260  			pos:         4,
   261  			count:       1,
   262  			expectedPos: 9,
   263  		},
   264  		{
   265  			name:        "next word past empty line",
   266  			inputString: "abc\n\n   123   xyz",
   267  			pos:         2,
   268  			count:       1,
   269  			expectedPos: 10,
   270  		},
   271  		{
   272  			name:        "empty line to next word",
   273  			inputString: "abc\n\n   123  xyz",
   274  			pos:         4,
   275  			count:       1,
   276  			expectedPos: 10,
   277  		},
   278  		{
   279  			name:        "punctuation",
   280  			inputString: "abc/def/ghi",
   281  			pos:         1,
   282  			count:       1,
   283  			expectedPos: 2,
   284  		},
   285  		{
   286  			name:        "punctuation",
   287  			inputString: "abc/def/ghi",
   288  			pos:         1,
   289  			count:       1,
   290  			expectedPos: 10,
   291  			withPunct:   true,
   292  		},
   293  		{
   294  			name:        "last word in document, third to last character",
   295  			inputString: "foo bar",
   296  			pos:         4,
   297  			count:       1,
   298  			expectedPos: 6,
   299  		},
   300  		{
   301  			name:        "last word in document, second to last character",
   302  			inputString: "foo bar",
   303  			pos:         5,
   304  			count:       1,
   305  			expectedPos: 6,
   306  		},
   307  		{
   308  			name:        "last word in document, last character",
   309  			inputString: "foo bar",
   310  			pos:         6,
   311  			count:       1,
   312  			expectedPos: 6,
   313  		},
   314  		{
   315  			name:        "count zero",
   316  			inputString: "lorem ipsum dolor sit amet",
   317  			pos:         2,
   318  			count:       0,
   319  			expectedPos: 2,
   320  		},
   321  		{
   322  			name:        "count three",
   323  			inputString: "lorem ipsum dolor sit amet",
   324  			pos:         2,
   325  			count:       3,
   326  			expectedPos: 16,
   327  		},
   328  	}
   329  
   330  	for _, tc := range testCases {
   331  		t.Run(tc.name, func(t *testing.T) {
   332  			textTree, err := text.NewTreeFromString(tc.inputString)
   333  			require.NoError(t, err)
   334  			actualPos := NextWordEnd(textTree, tc.pos, tc.count, tc.withPunct)
   335  			assert.Equal(t, tc.expectedPos, actualPos)
   336  		})
   337  	}
   338  }
   339  
   340  func TestPrevWordStart(t *testing.T) {
   341  	testCases := []struct {
   342  		name        string
   343  		inputString string
   344  		pos         uint64
   345  		count       uint64
   346  		expectedPos uint64
   347  		withPunct   bool
   348  	}{
   349  		{
   350  			name:        "empty",
   351  			inputString: "",
   352  			pos:         0,
   353  			count:       1,
   354  			expectedPos: 0,
   355  		},
   356  		{
   357  			name:        "prev word from current word, same line",
   358  			inputString: "abc   defg   hij",
   359  			pos:         6,
   360  			count:       1,
   361  			expectedPos: 0,
   362  		},
   363  		{
   364  			name:        "prev word from whitespace, same line",
   365  			inputString: "abc   defg   hij",
   366  			pos:         12,
   367  			count:       1,
   368  			expectedPos: 6,
   369  		},
   370  		{
   371  			name:        "prev word from different line",
   372  			inputString: "abc\n   123",
   373  			pos:         7,
   374  			count:       1,
   375  			expectedPos: 0,
   376  		},
   377  		{
   378  			name:        "prev word to empty line",
   379  			inputString: "abc\n\n   123",
   380  			pos:         8,
   381  			count:       1,
   382  			expectedPos: 4,
   383  		},
   384  		{
   385  			name:        "empty line to prev word",
   386  			inputString: "abc\n\n   123",
   387  			pos:         4,
   388  			count:       1,
   389  			expectedPos: 0,
   390  		},
   391  		{
   392  			name:        "multiple empty lines",
   393  			inputString: "\n\n\n\n",
   394  			pos:         2,
   395  			count:       1,
   396  			expectedPos: 1,
   397  		},
   398  		{
   399  			name:        "punctuation",
   400  			inputString: "abc/def/ghi",
   401  			pos:         5,
   402  			count:       1,
   403  			expectedPos: 4,
   404  		},
   405  		{
   406  			name:        "line ending with punctuation",
   407  			inputString: "abc.\n123",
   408  			pos:         5,
   409  			count:       1,
   410  			expectedPos: 3,
   411  		},
   412  		{
   413  			name:        "punctuation before whitespace",
   414  			inputString: "-   foo",
   415  			pos:         4,
   416  			count:       1,
   417  			expectedPos: 0,
   418  		},
   419  		{
   420  			name:        "count zero",
   421  			inputString: "lorem ipsum dolor sit amet",
   422  			pos:         18,
   423  			count:       0,
   424  			expectedPos: 18,
   425  		},
   426  		{
   427  			name:        "count three",
   428  			inputString: "lorem ipsum dolor sit amet",
   429  			pos:         25,
   430  			count:       3,
   431  			expectedPos: 12,
   432  		},
   433  		{
   434  			name:        "count three, with punctuation",
   435  			inputString: "lorem, ipsum, dolor, sit, amet",
   436  			withPunct:   true,
   437  			pos:         29,
   438  			count:       3,
   439  			expectedPos: 14,
   440  		},
   441  	}
   442  
   443  	for _, tc := range testCases {
   444  		t.Run(tc.name, func(t *testing.T) {
   445  			textTree, err := text.NewTreeFromString(tc.inputString)
   446  			require.NoError(t, err)
   447  			actualPos := PrevWordStart(textTree, tc.pos, tc.count, tc.withPunct)
   448  			assert.Equal(t, tc.expectedPos, actualPos)
   449  		})
   450  	}
   451  }
   452  
   453  func TestWordObject(t *testing.T) {
   454  	testCases := []struct {
   455  		name             string
   456  		inputString      string
   457  		pos              uint64
   458  		count            uint64
   459  		expectedStartPos uint64
   460  		expectedEndPos   uint64
   461  	}{
   462  		{
   463  			name:             "empty",
   464  			inputString:      "",
   465  			pos:              0,
   466  			count:            1,
   467  			expectedStartPos: 0,
   468  			expectedEndPos:   0,
   469  		},
   470  		{
   471  			name:             "on start of leading whitespace before word",
   472  			inputString:      "abc   def  ghi",
   473  			pos:              3,
   474  			count:            1,
   475  			expectedStartPos: 3,
   476  			expectedEndPos:   9,
   477  		},
   478  		{
   479  			name:             "on middle of leading whitespace before word",
   480  			inputString:      "abc   def  ghi",
   481  			pos:              4,
   482  			count:            1,
   483  			expectedStartPos: 3,
   484  			expectedEndPos:   9,
   485  		},
   486  		{
   487  			name:             "on end of leading whitespace before word",
   488  			inputString:      "abc   def  ghi",
   489  			pos:              5,
   490  			count:            1,
   491  			expectedStartPos: 3,
   492  			expectedEndPos:   9,
   493  		},
   494  		{
   495  			name:             "on start of word with trailing whitespace",
   496  			inputString:      "abc def    ghi",
   497  			pos:              4,
   498  			count:            1,
   499  			expectedStartPos: 4,
   500  			expectedEndPos:   11,
   501  		},
   502  		{
   503  			name:             "on middle of word with trailing whitespace",
   504  			inputString:      "abc def    ghi",
   505  			pos:              5,
   506  			count:            1,
   507  			expectedStartPos: 4,
   508  			expectedEndPos:   11,
   509  		},
   510  		{
   511  			name:             "on end of word with trailing whitespace",
   512  			inputString:      "abc def    ghi",
   513  			pos:              6,
   514  			count:            1,
   515  			expectedStartPos: 4,
   516  			expectedEndPos:   11,
   517  		},
   518  		{
   519  			name:             "start of word after punctuation",
   520  			inputString:      "abc/def/ghi",
   521  			pos:              4,
   522  			count:            1,
   523  			expectedStartPos: 4,
   524  			expectedEndPos:   7,
   525  		},
   526  		{
   527  			name:             "middle of word after punctuation",
   528  			inputString:      "abc/def/ghi",
   529  			pos:              5,
   530  			count:            1,
   531  			expectedStartPos: 4,
   532  			expectedEndPos:   7,
   533  		},
   534  		{
   535  			name:             "end of word after punctuation",
   536  			inputString:      "abc/def/ghi",
   537  			pos:              6,
   538  			count:            1,
   539  			expectedStartPos: 4,
   540  			expectedEndPos:   7,
   541  		},
   542  		{
   543  			name:             "on punctuation surrounded by words",
   544  			inputString:      "abc/def/ghi",
   545  			pos:              3,
   546  			count:            1,
   547  			expectedStartPos: 3,
   548  			expectedEndPos:   4,
   549  		},
   550  		{
   551  			name:             "on punctuation surrounded by whitespace",
   552  			inputString:      "a   /   b",
   553  			pos:              4,
   554  			count:            1,
   555  			expectedStartPos: 4,
   556  			expectedEndPos:   8,
   557  		},
   558  		{
   559  			name:             "on multiple punctuation chars",
   560  			inputString:      "abc///ghi",
   561  			pos:              4,
   562  			count:            1,
   563  			expectedStartPos: 3,
   564  			expectedEndPos:   6,
   565  		},
   566  		{
   567  			name:             "on leading whitespace before punctuation",
   568  			inputString:      "foo  {bar",
   569  			pos:              3,
   570  			count:            1,
   571  			expectedStartPos: 3,
   572  			expectedEndPos:   6,
   573  		},
   574  		{
   575  			name:             "whitespace at start of line",
   576  			inputString:      "abc\n    xyz",
   577  			pos:              6,
   578  			count:            1,
   579  			expectedStartPos: 4,
   580  			expectedEndPos:   11,
   581  		},
   582  		{
   583  
   584  			name:             "empty line, indentation",
   585  			inputString:      "abc\n\n   123",
   586  			pos:              4,
   587  			count:            1,
   588  			expectedStartPos: 4,
   589  			expectedEndPos:   11,
   590  		},
   591  		{
   592  
   593  			name:             "empty line, no indentation",
   594  			inputString:      "abc\n\n123",
   595  			pos:              4,
   596  			count:            1,
   597  			expectedStartPos: 4,
   598  			expectedEndPos:   8,
   599  		},
   600  		{
   601  			name:             "start of word at end of document",
   602  			inputString:      "abcd",
   603  			pos:              0,
   604  			count:            1,
   605  			expectedStartPos: 0,
   606  			expectedEndPos:   4,
   607  		},
   608  		{
   609  			name:             "middle of word at end of document",
   610  			inputString:      "abcd",
   611  			pos:              2,
   612  			count:            1,
   613  			expectedStartPos: 0,
   614  			expectedEndPos:   4,
   615  		},
   616  		{
   617  			name:             "end of word at end of document",
   618  			inputString:      "abcd",
   619  			pos:              3,
   620  			count:            1,
   621  			expectedStartPos: 0,
   622  			expectedEndPos:   4,
   623  		},
   624  
   625  		{
   626  			name:             "on word before whitespace at end of document",
   627  			inputString:      "abc    ",
   628  			pos:              2,
   629  			count:            1,
   630  			expectedStartPos: 0,
   631  			expectedEndPos:   7,
   632  		},
   633  		{
   634  			name:             "on whitespace at end of document",
   635  			inputString:      "abc    ",
   636  			pos:              4,
   637  			count:            1,
   638  			expectedStartPos: 3,
   639  			expectedEndPos:   7,
   640  		},
   641  		{
   642  			name:             "count zero",
   643  			inputString:      "lorem ipsum dolor sit amet",
   644  			pos:              7,
   645  			count:            0,
   646  			expectedStartPos: 7,
   647  			expectedEndPos:   7,
   648  		},
   649  		{
   650  			name:             "count three with leading whitespace",
   651  			inputString:      "lorem ipsum dolor sit amet",
   652  			pos:              5,
   653  			count:            3,
   654  			expectedStartPos: 5,
   655  			expectedEndPos:   21,
   656  		},
   657  		{
   658  			name:             "count three with trailing whitespace",
   659  			inputString:      "lorem ipsum dolor sit amet",
   660  			pos:              7,
   661  			count:            3,
   662  			expectedStartPos: 6,
   663  			expectedEndPos:   22,
   664  		},
   665  		{
   666  			name:             "count three with punctuation",
   667  			inputString:      "lorem.ipsum.dolor.sit.amet",
   668  			pos:              5,
   669  			count:            3,
   670  			expectedStartPos: 5,
   671  			expectedEndPos:   12,
   672  		},
   673  		{
   674  			name:             "count three with punctuation with leading whitespace",
   675  			inputString:      "lorem   ipsum.dolor.sit.amet",
   676  			pos:              6,
   677  			count:            3,
   678  			expectedStartPos: 5,
   679  			expectedEndPos:   19,
   680  		},
   681  		{
   682  			name:             "count with multiple whitespace, leading whitespace",
   683  			inputString:      "lorem     ipsum      dolor     sit      amet",
   684  			pos:              7,
   685  			count:            2,
   686  			expectedStartPos: 5,
   687  			expectedEndPos:   26,
   688  		},
   689  		{
   690  			name:             "count with multiple whitespace, trailing whitespace",
   691  			inputString:      "lorem     ipsum      dolor     sit      amet",
   692  			pos:              2,
   693  			count:            2,
   694  			expectedStartPos: 0,
   695  			expectedEndPos:   21,
   696  		},
   697  	}
   698  
   699  	for _, tc := range testCases {
   700  		t.Run(tc.name, func(t *testing.T) {
   701  			textTree, err := text.NewTreeFromString(tc.inputString)
   702  			require.NoError(t, err)
   703  			startPos, endPos := WordObject(textTree, tc.pos, tc.count)
   704  			assert.Equal(t, tc.expectedStartPos, startPos)
   705  			assert.Equal(t, tc.expectedEndPos, endPos)
   706  		})
   707  	}
   708  }
   709  
   710  func TestInnerWordObject(t *testing.T) {
   711  	testCases := []struct {
   712  		name             string
   713  		inputString      string
   714  		pos              uint64
   715  		count            uint64
   716  		expectedStartPos uint64
   717  		expectedEndPos   uint64
   718  	}{
   719  		{
   720  			name:             "empty",
   721  			inputString:      "",
   722  			pos:              0,
   723  			count:            1,
   724  			expectedStartPos: 0,
   725  			expectedEndPos:   0,
   726  		},
   727  		{
   728  			name:             "on start of leading whitespace before word",
   729  			inputString:      "abc   def  ghi",
   730  			pos:              3,
   731  			count:            1,
   732  			expectedStartPos: 3,
   733  			expectedEndPos:   6,
   734  		},
   735  		{
   736  			name:             "on middle of leading whitespace before word",
   737  			inputString:      "abc   def  ghi",
   738  			pos:              4,
   739  			count:            1,
   740  			expectedStartPos: 3,
   741  			expectedEndPos:   6,
   742  		},
   743  		{
   744  			name:             "on end of leading whitespace before word",
   745  			inputString:      "abc   def  ghi",
   746  			pos:              5,
   747  			count:            1,
   748  			expectedStartPos: 3,
   749  			expectedEndPos:   6,
   750  		},
   751  		{
   752  			name:             "on start of word with trailing whitespace",
   753  			inputString:      "abc def    ghi",
   754  			pos:              4,
   755  			count:            1,
   756  			expectedStartPos: 4,
   757  			expectedEndPos:   7,
   758  		},
   759  		{
   760  			name:             "on middle of word with trailing whitespace",
   761  			inputString:      "abc def    ghi",
   762  			pos:              5,
   763  			count:            1,
   764  			expectedStartPos: 4,
   765  			expectedEndPos:   7,
   766  		},
   767  		{
   768  			name:             "on end of word with trailing whitespace",
   769  			inputString:      "abc def    ghi",
   770  			pos:              6,
   771  			count:            1,
   772  			expectedStartPos: 4,
   773  			expectedEndPos:   7,
   774  		},
   775  		{
   776  			name:             "start of word after punctuation",
   777  			inputString:      "abc/def/ghi",
   778  			pos:              4,
   779  			count:            1,
   780  			expectedStartPos: 4,
   781  			expectedEndPos:   7,
   782  		},
   783  		{
   784  			name:             "middle of word after punctuation",
   785  			inputString:      "abc/def/ghi",
   786  			pos:              5,
   787  			count:            1,
   788  			expectedStartPos: 4,
   789  			expectedEndPos:   7,
   790  		},
   791  		{
   792  			name:             "end of word after punctuation",
   793  			inputString:      "abc/def/ghi",
   794  			pos:              6,
   795  			count:            1,
   796  			expectedStartPos: 4,
   797  			expectedEndPos:   7,
   798  		},
   799  		{
   800  			name:             "on punctuation surrounded by words",
   801  			inputString:      "abc/def/ghi",
   802  			pos:              3,
   803  			count:            1,
   804  			expectedStartPos: 3,
   805  			expectedEndPos:   4,
   806  		},
   807  		{
   808  			name:             "on punctuation surrounded by whitespace",
   809  			inputString:      "a   /   b",
   810  			pos:              4,
   811  			count:            1,
   812  			expectedStartPos: 4,
   813  			expectedEndPos:   5,
   814  		},
   815  		{
   816  			name:             "on multiple punctuation chars",
   817  			inputString:      "abc///ghi",
   818  			pos:              4,
   819  			count:            1,
   820  			expectedStartPos: 3,
   821  			expectedEndPos:   6,
   822  		},
   823  		{
   824  			name:             "on leading whitespace before punctuation",
   825  			inputString:      "foo  {bar",
   826  			pos:              3,
   827  			count:            1,
   828  			expectedStartPos: 3,
   829  			expectedEndPos:   5,
   830  		},
   831  		{
   832  			name:             "whitespace at start of line",
   833  			inputString:      "abc\n    xyz",
   834  			pos:              6,
   835  			count:            1,
   836  			expectedStartPos: 4,
   837  			expectedEndPos:   8,
   838  		},
   839  		{
   840  
   841  			name:             "empty line, indentation",
   842  			inputString:      "abc\n\n   123",
   843  			pos:              4,
   844  			count:            1,
   845  			expectedStartPos: 4,
   846  			expectedEndPos:   4,
   847  		},
   848  		{
   849  
   850  			name:             "empty line, no indentation",
   851  			inputString:      "abc\n\n123",
   852  			pos:              4,
   853  			count:            1,
   854  			expectedStartPos: 4,
   855  			expectedEndPos:   4,
   856  		},
   857  		{
   858  			name:             "word at end of line",
   859  			inputString:      "foo bar\nbaz\nbat",
   860  			pos:              5,
   861  			count:            1,
   862  			expectedStartPos: 4,
   863  			expectedEndPos:   7,
   864  		},
   865  		{
   866  			name:             "start of word at end of document",
   867  			inputString:      "abcd",
   868  			pos:              0,
   869  			count:            1,
   870  			expectedStartPos: 0,
   871  			expectedEndPos:   4,
   872  		},
   873  		{
   874  			name:             "middle of word at end of document",
   875  			inputString:      "abcd",
   876  			pos:              2,
   877  			count:            1,
   878  			expectedStartPos: 0,
   879  			expectedEndPos:   4,
   880  		},
   881  		{
   882  			name:             "end of word at end of document",
   883  			inputString:      "abcd",
   884  			pos:              3,
   885  			count:            1,
   886  			expectedStartPos: 0,
   887  			expectedEndPos:   4,
   888  		},
   889  
   890  		{
   891  			name:             "on word before whitespace at end of document",
   892  			inputString:      "abc    ",
   893  			pos:              2,
   894  			count:            1,
   895  			expectedStartPos: 0,
   896  			expectedEndPos:   3,
   897  		},
   898  		{
   899  			name:             "on whitespace at end of document",
   900  			inputString:      "abc    ",
   901  			pos:              4,
   902  			count:            1,
   903  			expectedStartPos: 3,
   904  			expectedEndPos:   7,
   905  		},
   906  		{
   907  			name:             "count zero",
   908  			inputString:      "lorem ipsum dolor sit amet",
   909  			pos:              7,
   910  			count:            0,
   911  			expectedStartPos: 7,
   912  			expectedEndPos:   7,
   913  		},
   914  		{
   915  			name:             "count three",
   916  			inputString:      "lorem ipsum dolor sit amet",
   917  			pos:              7,
   918  			count:            3,
   919  			expectedStartPos: 6,
   920  			expectedEndPos:   17,
   921  		},
   922  		{
   923  			name:             "count past end of line",
   924  			inputString:      "lorem ipsum\ndolor\nsit amet",
   925  			pos:              1,
   926  			count:            5,
   927  			expectedStartPos: 0,
   928  			expectedEndPos:   21,
   929  		},
   930  	}
   931  
   932  	for _, tc := range testCases {
   933  		t.Run(tc.name, func(t *testing.T) {
   934  			textTree, err := text.NewTreeFromString(tc.inputString)
   935  			require.NoError(t, err)
   936  			startPos, endPos := InnerWordObject(textTree, tc.pos, tc.count)
   937  			assert.Equal(t, tc.expectedStartPos, startPos)
   938  			assert.Equal(t, tc.expectedEndPos, endPos)
   939  		})
   940  	}
   941  }
   942  
   943  func TestIsPunct(t *testing.T) {
   944  	testCases := []struct {
   945  		r           rune
   946  		expectPunct bool
   947  	}{
   948  		{r: '\x00', expectPunct: false},
   949  		{r: '\x01', expectPunct: false},
   950  		{r: '\x02', expectPunct: false},
   951  		{r: '\x03', expectPunct: false},
   952  		{r: '\x04', expectPunct: false},
   953  		{r: '\x05', expectPunct: false},
   954  		{r: '\x06', expectPunct: false},
   955  		{r: '\a', expectPunct: false},
   956  		{r: '\b', expectPunct: false},
   957  		{r: '\t', expectPunct: false},
   958  		{r: '\n', expectPunct: false},
   959  		{r: '\v', expectPunct: false},
   960  		{r: '\f', expectPunct: false},
   961  		{r: '\r', expectPunct: false},
   962  		{r: '\x0e', expectPunct: false},
   963  		{r: '\x0f', expectPunct: false},
   964  		{r: '\x10', expectPunct: false},
   965  		{r: '\x11', expectPunct: false},
   966  		{r: '\x12', expectPunct: false},
   967  		{r: '\x13', expectPunct: false},
   968  		{r: '\x14', expectPunct: false},
   969  		{r: '\x15', expectPunct: false},
   970  		{r: '\x16', expectPunct: false},
   971  		{r: '\x17', expectPunct: false},
   972  		{r: '\x18', expectPunct: false},
   973  		{r: '\x19', expectPunct: false},
   974  		{r: '\x1a', expectPunct: false},
   975  		{r: '\x1b', expectPunct: false},
   976  		{r: '\x1c', expectPunct: false},
   977  		{r: '\x1d', expectPunct: false},
   978  		{r: '\x1e', expectPunct: false},
   979  		{r: '\x1f', expectPunct: false},
   980  		{r: ' ', expectPunct: false},
   981  		{r: '!', expectPunct: true},
   982  		{r: '"', expectPunct: true},
   983  		{r: '#', expectPunct: true},
   984  		{r: '$', expectPunct: true},
   985  		{r: '%', expectPunct: true},
   986  		{r: '&', expectPunct: true},
   987  		{r: '\'', expectPunct: true},
   988  		{r: '(', expectPunct: true},
   989  		{r: ')', expectPunct: true},
   990  		{r: '*', expectPunct: true},
   991  		{r: '+', expectPunct: true},
   992  		{r: ',', expectPunct: true},
   993  		{r: '-', expectPunct: true},
   994  		{r: '.', expectPunct: true},
   995  		{r: '/', expectPunct: true},
   996  		{r: '0', expectPunct: false},
   997  		{r: '1', expectPunct: false},
   998  		{r: '2', expectPunct: false},
   999  		{r: '3', expectPunct: false},
  1000  		{r: '4', expectPunct: false},
  1001  		{r: '5', expectPunct: false},
  1002  		{r: '6', expectPunct: false},
  1003  		{r: '7', expectPunct: false},
  1004  		{r: '8', expectPunct: false},
  1005  		{r: '9', expectPunct: false},
  1006  		{r: ':', expectPunct: true},
  1007  		{r: ';', expectPunct: true},
  1008  		{r: '<', expectPunct: true},
  1009  		{r: '=', expectPunct: true},
  1010  		{r: '>', expectPunct: true},
  1011  		{r: '?', expectPunct: true},
  1012  		{r: '@', expectPunct: true},
  1013  		{r: 'A', expectPunct: false},
  1014  		{r: 'B', expectPunct: false},
  1015  		{r: 'C', expectPunct: false},
  1016  		{r: 'D', expectPunct: false},
  1017  		{r: 'E', expectPunct: false},
  1018  		{r: 'F', expectPunct: false},
  1019  		{r: 'G', expectPunct: false},
  1020  		{r: 'H', expectPunct: false},
  1021  		{r: 'I', expectPunct: false},
  1022  		{r: 'J', expectPunct: false},
  1023  		{r: 'K', expectPunct: false},
  1024  		{r: 'L', expectPunct: false},
  1025  		{r: 'M', expectPunct: false},
  1026  		{r: 'N', expectPunct: false},
  1027  		{r: 'O', expectPunct: false},
  1028  		{r: 'P', expectPunct: false},
  1029  		{r: 'Q', expectPunct: false},
  1030  		{r: 'R', expectPunct: false},
  1031  		{r: 'S', expectPunct: false},
  1032  		{r: 'T', expectPunct: false},
  1033  		{r: 'U', expectPunct: false},
  1034  		{r: 'V', expectPunct: false},
  1035  		{r: 'W', expectPunct: false},
  1036  		{r: 'X', expectPunct: false},
  1037  		{r: 'Y', expectPunct: false},
  1038  		{r: 'Z', expectPunct: false},
  1039  		{r: '[', expectPunct: true},
  1040  		{r: '\\', expectPunct: true},
  1041  		{r: ']', expectPunct: true},
  1042  		{r: '^', expectPunct: true},
  1043  		{r: '_', expectPunct: false},
  1044  		{r: '`', expectPunct: true},
  1045  		{r: 'a', expectPunct: false},
  1046  		{r: 'b', expectPunct: false},
  1047  		{r: 'c', expectPunct: false},
  1048  		{r: 'd', expectPunct: false},
  1049  		{r: 'e', expectPunct: false},
  1050  		{r: 'f', expectPunct: false},
  1051  		{r: 'g', expectPunct: false},
  1052  		{r: 'h', expectPunct: false},
  1053  		{r: 'i', expectPunct: false},
  1054  		{r: 'j', expectPunct: false},
  1055  		{r: 'k', expectPunct: false},
  1056  		{r: 'l', expectPunct: false},
  1057  		{r: 'm', expectPunct: false},
  1058  		{r: 'n', expectPunct: false},
  1059  		{r: 'o', expectPunct: false},
  1060  		{r: 'p', expectPunct: false},
  1061  		{r: 'q', expectPunct: false},
  1062  		{r: 'r', expectPunct: false},
  1063  		{r: 's', expectPunct: false},
  1064  		{r: 't', expectPunct: false},
  1065  		{r: 'u', expectPunct: false},
  1066  		{r: 'v', expectPunct: false},
  1067  		{r: 'w', expectPunct: false},
  1068  		{r: 'x', expectPunct: false},
  1069  		{r: 'y', expectPunct: false},
  1070  		{r: 'z', expectPunct: false},
  1071  		{r: '{', expectPunct: true},
  1072  		{r: '|', expectPunct: true},
  1073  		{r: '}', expectPunct: true},
  1074  		{r: '~', expectPunct: true},
  1075  		{r: '\u007f', expectPunct: false},
  1076  	}
  1077  
  1078  	for _, tc := range testCases {
  1079  		t.Run(fmt.Sprintf("%q", tc.r), func(t *testing.T) {
  1080  			seg := segment.Empty()
  1081  			seg.Append(tc.r)
  1082  			assert.Equal(t, tc.expectPunct, isPunct(seg))
  1083  		})
  1084  	}
  1085  }