Patch 8.2.2605
Problem:    Vim9: string index and slice does not include composing chars.
Solution:   Include composing characters. (issue #6563)
Files:      runtime/doc/vim9.txt, src/vim9execute.c,
            src/testdir/test_vim9_expr.vim


*** ../vim-8.2.2604/runtime/doc/vim9.txt        2021-02-19 21:42:51.540789780 
+0100
--- runtime/doc/vim9.txt        2021-03-14 18:31:50.342134768 +0100
***************
*** 96,103 ****
        def CallMe(count: number, message: string): bool
  - Call functions without `:call`: >
        writefile(['done'], 'file.txt')
! - You cannot use `:xit`, `:t`, `:k`, `:append`, `:change`, `:insert` or
!   curly-braces names.
  - A range before a command must be prefixed with a colon: >
        :%s/this/that
  - Unless mentioned specifically, the highest |scriptversion| is used.
--- 96,103 ----
        def CallMe(count: number, message: string): bool
  - Call functions without `:call`: >
        writefile(['done'], 'file.txt')
! - You cannot use `:xit`, `:t`, `:k`, `:append`, `:change`, `:insert`, `:open`
!   or curly-braces names.
  - A range before a command must be prefixed with a colon: >
        :%s/this/that
  - Unless mentioned specifically, the highest |scriptversion| is used.
***************
*** 279,286 ****
  variables, because they are not really declared.  They can also be deleted
  with `:unlet`.
  
! Variables and functions cannot shadow previously defined or imported variables
! and functions.
  Variables may shadow Ex commands, rename the variable if needed.
  
  Global variables and user defined functions must be prefixed with "g:", also
--- 279,286 ----
  variables, because they are not really declared.  They can also be deleted
  with `:unlet`.
  
! Variables, functions and function arguments cannot shadow previously defined
! or imported variables and functions in the same script file.
  Variables may shadow Ex commands, rename the variable if needed.
  
  Global variables and user defined functions must be prefixed with "g:", also
***************
*** 307,320 ****
        const myList = [1, 2]
        myList = [3, 4]         # Error!
        myList[0] = 9           # Error!
!       muList->add(3)          # Error!
  <                                                     *:final*
  `:final` is used for making only the variable a constant, the value can be
  changed.  This is well known from Java.  Example: >
        final myList = [1, 2]
        myList = [3, 4]         # Error!
        myList[0] = 9           # OK
!       muList->add(3)          # OK
  
  It is common to write constants as ALL_CAPS, but you don't have to.
  
--- 307,320 ----
        const myList = [1, 2]
        myList = [3, 4]         # Error!
        myList[0] = 9           # Error!
!       myList->add(3)          # Error!
  <                                                     *:final*
  `:final` is used for making only the variable a constant, the value can be
  changed.  This is well known from Java.  Example: >
        final myList = [1, 2]
        myList = [3, 4]         # Error!
        myList[0] = 9           # OK
!       myList->add(3)          # OK
  
  It is common to write constants as ALL_CAPS, but you don't have to.
  
***************
*** 341,347 ****
  Using `:call` is still possible, but this is discouraged.
  
  A method call without `eval` is possible, so long as the start is an
! identifier or can't be an Ex command.  Examples: >
        myList->add(123)
        g:myList->add(123)
        [1, 2, 3]->Process()
--- 341,348 ----
  Using `:call` is still possible, but this is discouraged.
  
  A method call without `eval` is possible, so long as the start is an
! identifier or can't be an Ex command.  For a function either "(" or "->" must
! be following, without a line break.  Examples: >
        myList->add(123)
        g:myList->add(123)
        [1, 2, 3]->Process()
***************
*** 412,418 ****
  
                                                        *vim9-curly*
  To avoid the "{" of a dictionary literal to be recognized as a statement block
! wrap it in parenthesis: >
        var Lambda = (arg) => ({key: 42})
  
  Also when confused with the start of a command block: >
--- 413,419 ----
  
                                                        *vim9-curly*
  To avoid the "{" of a dictionary literal to be recognized as a statement block
! wrap it in parentheses: >
        var Lambda = (arg) => ({key: 42})
  
  Also when confused with the start of a command block: >
***************
*** 696,703 ****
  used, not "v:false" and "v:true" like in legacy script.  "v:none" is not
  changed, it is only used in JSON and has no equivalent in other languages.
  
! Indexing a string with [idx] or [idx : idx] uses character indexes instead of
! byte indexes. Example: >
        echo 'bár'[1]
  In legacy script this results in the character 0xc3 (an illegal byte), in Vim9
  script this results in the string 'á'.
--- 697,705 ----
  used, not "v:false" and "v:true" like in legacy script.  "v:none" is not
  changed, it is only used in JSON and has no equivalent in other languages.
  
! Indexing a string with [idx] or taking a slice with [idx : idx] uses character
! indexes instead of byte indexes.  Composing characters are included.
! Example: >
        echo 'bár'[1]
  In legacy script this results in the character 0xc3 (an illegal byte), in Vim9
  script this results in the string 'á'.
***************
*** 800,805 ****
--- 802,809 ----
  The 'edcompatible' option value is not used.
  The 'gdefault' option value is not used.
  
+ You may also find this wiki useful.  It was written by an early adoptor of
+ Vim9 script: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
  
  ==============================================================================
  
***************
*** 1029,1038 ****
  - Using a number where a string is expected.   *E1024*
  
  One consequence is that the item type of a list or dict given to map() must
! not change.  This will give an error in compiled code: >
        map([1, 2, 3], (i, v) => 'item ' .. i)
!       E1012: Type mismatch; expected list<number> but got list<string>
! Instead use |mapnew()|.
  
  ==============================================================================
  
--- 1033,1046 ----
  - Using a number where a string is expected.   *E1024*
  
  One consequence is that the item type of a list or dict given to map() must
! not change.  This will give an error in Vim9 script: >
        map([1, 2, 3], (i, v) => 'item ' .. i)
!       E1012: Type mismatch; expected number but got string
! Instead use |mapnew()|.  If the item type was determined to be "any" it can
! change to a more specific type.  E.g. when a list of mixed types gets changed
! to a list of numbers.
! Same for |extend()|, use |extendnew()| instead, and for |flatten()|, use
! |flattennew()| instead.
  
  ==============================================================================
  
***************
*** 1084,1090 ****
        vim9script
        # Vim9 script commands go here
  This allows for writing a script that takes advantage of the Vim9 script
! syntax if possible, but will also work on an Vim version without it.
  
  This can only work in two ways:
  1. The "if" statement evaluates to false, the commands up to `endif` are
--- 1092,1098 ----
        vim9script
        # Vim9 script commands go here
  This allows for writing a script that takes advantage of the Vim9 script
! syntax if possible, but will also work on a Vim version without it.
  
  This can only work in two ways:
  1. The "if" statement evaluates to false, the commands up to `endif` are
***************
*** 1107,1113 ****
        export class MyClass ...
  
  As this suggests, only constants, variables, `:def` functions and classes can
! be exported. {classes are not implemented yet}
  
                                                        *E1042*
  `:export` can only be used in Vim9 script, at the script level.
--- 1115,1121 ----
        export class MyClass ...
  
  As this suggests, only constants, variables, `:def` functions and classes can
! be exported. {not implemented yet: export class}
  
                                                        *E1042*
  `:export` can only be used in Vim9 script, at the script level.
*** ../vim-8.2.2604/src/vim9execute.c   2021-03-14 12:13:30.192279488 +0100
--- src/vim9execute.c   2021-03-14 18:35:58.069458973 +0100
***************
*** 985,992 ****
  }
  
  /*
!  * Return the character "str[index]" where "index" is the character index.  If
!  * "index" is out of range NULL is returned.
   */
      char_u *
  char_from_string(char_u *str, varnumber_T index)
--- 985,993 ----
  }
  
  /*
!  * Return the character "str[index]" where "index" is the character index,
!  * including composing characters.
!  * If "index" is out of range NULL is returned.
   */
      char_u *
  char_from_string(char_u *str, varnumber_T index)
***************
*** 1005,1011 ****
        int     clen = 0;
  
        for (nbyte = 0; nbyte < slen; ++clen)
!           nbyte += MB_CPTR2LEN(str + nbyte);
        nchar = clen + index;
        if (nchar < 0)
            // unlike list: index out of range results in empty string
--- 1006,1012 ----
        int     clen = 0;
  
        for (nbyte = 0; nbyte < slen; ++clen)
!           nbyte += mb_ptr2len(str + nbyte);
        nchar = clen + index;
        if (nchar < 0)
            // unlike list: index out of range results in empty string
***************
*** 1013,1027 ****
      }
  
      for (nbyte = 0; nchar > 0 && nbyte < slen; --nchar)
!       nbyte += MB_CPTR2LEN(str + nbyte);
      if (nbyte >= slen)
        return NULL;
!     return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte));
  }
  
  /*
   * Get the byte index for character index "idx" in string "str" with length
!  * "str_len".
   * If going over the end return "str_len".
   * If "idx" is negative count from the end, -1 is the last character.
   * When going over the start return -1.
--- 1014,1028 ----
      }
  
      for (nbyte = 0; nchar > 0 && nbyte < slen; --nchar)
!       nbyte += mb_ptr2len(str + nbyte);
      if (nbyte >= slen)
        return NULL;
!     return vim_strnsave(str + nbyte, mb_ptr2len(str + nbyte));
  }
  
  /*
   * Get the byte index for character index "idx" in string "str" with length
!  * "str_len".  Composing characters are included.
   * If going over the end return "str_len".
   * If "idx" is negative count from the end, -1 is the last character.
   * When going over the start return -1.
***************
*** 1036,1042 ****
      {
        while (nchar > 0 && nbyte < str_len)
        {
!           nbyte += MB_CPTR2LEN(str + nbyte);
            --nchar;
        }
      }
--- 1037,1043 ----
      {
        while (nchar > 0 && nbyte < str_len)
        {
!           nbyte += mb_ptr2len(str + nbyte);
            --nchar;
        }
      }
***************
*** 1056,1062 ****
  }
  
  /*
!  * Return the slice "str[first:last]" using character indexes.
   * "exclusive" is TRUE for slice().
   * Return NULL when the result is empty.
   */
--- 1057,1064 ----
  }
  
  /*
!  * Return the slice "str[first : last]" using character indexes.  Composing
!  * characters are included.
   * "exclusive" is TRUE for slice().
   * Return NULL when the result is empty.
   */
***************
*** 1079,1085 ****
        end_byte = char_idx2byte(str, slen, last);
        if (!exclusive && end_byte >= 0 && end_byte < (long)slen)
            // end index is inclusive
!           end_byte += MB_CPTR2LEN(str + end_byte);
      }
  
      if (start_byte >= (long)slen || end_byte <= start_byte)
--- 1081,1087 ----
        end_byte = char_idx2byte(str, slen, last);
        if (!exclusive && end_byte >= 0 && end_byte < (long)slen)
            // end index is inclusive
!           end_byte += mb_ptr2len(str + end_byte);
      }
  
      if (start_byte >= (long)slen || end_byte <= start_byte)
***************
*** 3249,3256 ****
                        res = string_slice(tv->vval.v_string, n1, n2, FALSE);
                    else
                        // Index: The resulting variable is a string of a
!                       // single character.  If the index is too big or
!                       // negative the result is empty.
                        res = char_from_string(tv->vval.v_string, n2);
                    vim_free(tv->vval.v_string);
                    tv->vval.v_string = res;
--- 3251,3259 ----
                        res = string_slice(tv->vval.v_string, n1, n2, FALSE);
                    else
                        // Index: The resulting variable is a string of a
!                       // single character (including composing characters).
!                       // If the index is too big or negative the result is
!                       // empty.
                        res = char_from_string(tv->vval.v_string, n2);
                    vim_free(tv->vval.v_string);
                    tv->vval.v_string = res;
*** ../vim-8.2.2604/src/testdir/test_vim9_expr.vim      2021-03-10 
18:43:05.573396183 +0100
--- src/testdir/test_vim9_expr.vim      2021-03-14 18:37:13.489101329 +0100
***************
*** 2367,2372 ****
--- 2367,2401 ----
      assert_equal('abcd', g:teststring[: -3])
      assert_equal('', g:teststring[: -9])
  
+     # composing characters are included
+     g:teststring = 'àéû'
+     assert_equal('à', g:teststring[0])
+     assert_equal('é', g:teststring[1])
+     assert_equal('û', g:teststring[2])
+     assert_equal('', g:teststring[3])
+     assert_equal('', g:teststring[4])
+ 
+     assert_equal('û', g:teststring[-1])
+     assert_equal('é', g:teststring[-2])
+     assert_equal('à', g:teststring[-3])
+     assert_equal('', g:teststring[-4])
+     assert_equal('', g:teststring[-5])
+ 
+     assert_equal('à', g:teststring[0 : 0])
+     assert_equal('é', g:teststring[1 : 1])
+     assert_equal('àé', g:teststring[0 : 1])
+     assert_equal('àéû', g:teststring[0 : -1])
+     assert_equal('àé', g:teststring[0 : -2])
+     assert_equal('à', g:teststring[0 : -3])
+     assert_equal('', g:teststring[0 : -4])
+     assert_equal('', g:teststring[0 : -5])
+     assert_equal('àéû', g:teststring[ : ])
+     assert_equal('àéû', g:teststring[0 : ])
+     assert_equal('éû', g:teststring[1 : ])
+     assert_equal('û', g:teststring[2 : ])
+     assert_equal('', g:teststring[3 : ])
+     assert_equal('', g:teststring[4 : ])
+ 
      # blob index cannot be out of range
      g:testblob = 0z01ab
      assert_equal(0x01, g:testblob[0])
*** ../vim-8.2.2604/src/version.c       2021-03-14 16:20:33.158928849 +0100
--- src/version.c       2021-03-14 18:20:48.259532113 +0100
***************
*** 752,753 ****
--- 752,755 ----
  {   /* Add new patch number below this line */
+ /**/
+     2605,
  /**/

-- 
Your company is doomed if your primary product is overhead transparencies.
                                (Scott Adams - The Dilbert principle)

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202103141740.12EHerIH289431%40masaka.moolenaar.net.

Raspunde prin e-mail lui