mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	Merge #10086 'vim-patch:8.1.{902,1114}'
This commit is contained in:
		| @@ -610,10 +610,10 @@ Expression syntax summary, from least to most significant: | ||||
| 	expr2 ? expr1 : expr1	if-then-else | ||||
|  | ||||
| |expr2|	expr3 | ||||
| 	expr3 || expr3 ..	logical OR | ||||
| 	expr3 || expr3 ...	logical OR | ||||
|  | ||||
| |expr3|	expr4 | ||||
| 	expr4 && expr4 ..	logical AND | ||||
| 	expr4 && expr4 ...	logical AND | ||||
|  | ||||
| |expr4|	expr5 | ||||
| 	expr5 == expr5		equal | ||||
| @@ -634,14 +634,15 @@ Expression syntax summary, from least to most significant: | ||||
| 	expr5 isnot expr5	different |List| instance | ||||
|  | ||||
| |expr5|	expr6 | ||||
| 	expr6 +	 expr6 ..	number addition or list concatenation | ||||
| 	expr6 -	 expr6 ..	number subtraction | ||||
| 	expr6 .	 expr6 ..	string concatenation | ||||
| 	expr6 +	 expr6 ...	number addition, list or blob concatenation | ||||
| 	expr6 -	 expr6 ...	number subtraction | ||||
| 	expr6 .	 expr6 ...	string concatenation | ||||
| 	expr6 .. expr6 ...	string concatenation | ||||
|  | ||||
| |expr6|	expr7 | ||||
| 	expr7 *	 expr7 ..	number multiplication | ||||
| 	expr7 /	 expr7 ..	number division | ||||
| 	expr7 %	 expr7 ..	number modulo | ||||
| 	expr7 *	 expr7 ...	number multiplication | ||||
| 	expr7 /	 expr7 ...	number division | ||||
| 	expr7 %	 expr7 ...	number modulo | ||||
|  | ||||
| |expr7|	expr8 | ||||
| 	! expr7			logical NOT | ||||
| @@ -670,7 +671,7 @@ Expression syntax summary, from least to most significant: | ||||
| 	{args -> expr1}		lambda expression | ||||
|  | ||||
|  | ||||
| ".." indicates that the operations in this level can be concatenated. | ||||
| "..." indicates that the operations in this level can be concatenated. | ||||
| Example: > | ||||
| 	&nu || &list && &shell == "csh" | ||||
|  | ||||
| @@ -707,7 +708,9 @@ use in a variable such as "a:1". | ||||
| expr2 and expr3						*expr2* *expr3* | ||||
| --------------- | ||||
|  | ||||
| 					*expr-barbar* *expr-&&* | ||||
| expr3 || expr3 ..	logical OR		*expr-barbar* | ||||
| expr4 && expr4 ..	logical AND		*expr-&&* | ||||
|  | ||||
| The "||" and "&&" operators take one argument on each side.  The arguments | ||||
| are (converted to) Numbers.  The result is: | ||||
|  | ||||
| @@ -847,18 +850,22 @@ can be matched like an ordinary character.  Examples: | ||||
|  | ||||
| expr5 and expr6						*expr5* *expr6* | ||||
| --------------- | ||||
| expr6 +	 expr6 ..	Number addition or |List| concatenation	*expr-+* | ||||
| expr6 -	 expr6 ..	Number subtraction			*expr--* | ||||
| expr6 .	 expr6 ..	String concatenation			*expr-.* | ||||
| expr6 + expr6   Number addition, |List| or |Blob| concatenation	*expr-+* | ||||
| expr6 - expr6   Number subtraction				*expr--* | ||||
| expr6 . expr6   String concatenation				*expr-.* | ||||
| expr6 .. expr6  String concatenation				*expr-..* | ||||
|  | ||||
| For |Lists| only "+" is possible and then both expr6 must be a list.  The | ||||
| result is a new list with the two lists Concatenated. | ||||
|  | ||||
| expr7 *	 expr7 ..	Number multiplication			*expr-star* | ||||
| expr7 /	 expr7 ..	Number division				*expr-/* | ||||
| expr7 %	 expr7 ..	Number modulo				*expr-%* | ||||
| For String concatenation ".." is preferred, since "." is ambiguous, it is also | ||||
| used for |Dict| member access and floating point numbers. | ||||
|  | ||||
| For all, except ".", Strings are converted to Numbers. | ||||
| expr7 * expr7  Number multiplication				*expr-star* | ||||
| expr7 / expr7  Number division					*expr-/* | ||||
| expr7 % expr7  Number modulo					*expr-%* | ||||
|  | ||||
| For all, except "." and "..", Strings are converted to Numbers. | ||||
| For bitwise operators see |and()|, |or()| and |xor()|. | ||||
|  | ||||
| Note the difference between "+" and ".": | ||||
| @@ -1049,11 +1056,6 @@ These are INVALID: | ||||
| 	3.		empty {M} | ||||
| 	1e40		missing .{M} | ||||
|  | ||||
| 							*float-pi* *float-e* | ||||
| A few useful values to copy&paste: > | ||||
| 	:let pi = 3.14159265359 | ||||
| 	:let e  = 2.71828182846 | ||||
|  | ||||
| Rationale: | ||||
| Before floating point was introduced, the text "123.456" was interpreted as | ||||
| the two numbers "123" and "456", both converted to a string and concatenated, | ||||
| @@ -1062,6 +1064,15 @@ could not find it intentionally being used in Vim scripts, this backwards | ||||
| incompatibility was accepted in favor of being able to use the normal notation | ||||
| for floating point numbers. | ||||
|  | ||||
| 							*float-pi* *float-e* | ||||
| A few useful values to copy&paste: > | ||||
| 	:let pi = 3.14159265359 | ||||
| 	:let e  = 2.71828182846 | ||||
| Or, if you don't want to write them in as floating-point literals, you can | ||||
| also use functions, like the following: > | ||||
| 	:let pi = acos(-1.0) | ||||
| 	:let e  = exp(1.0) | ||||
| < | ||||
| 						*floating-point-precision* | ||||
| The precision and range of floating points numbers depends on what "double" | ||||
| means in the library Vim was compiled with.  There is no way to change this at | ||||
| @@ -1101,8 +1112,10 @@ A string constant accepts these special characters: | ||||
| \\	backslash | ||||
| \"	double quote | ||||
| \<xxx>	Special key named "xxx".  e.g. "\<C-W>" for CTRL-W.  This is for use | ||||
| 	in mappings, the 0x80 byte is escaped.  Don't use <Char-xxxx> to get a | ||||
| 	utf-8 character, use \uxxxx as mentioned above. | ||||
| 	in mappings, the 0x80 byte is escaped. | ||||
| 	To use the double quote character it must be escaped: "<M-\">". | ||||
| 	Don't use <Char-xxxx> to get a utf-8 character, use \uxxxx as | ||||
| 	mentioned above. | ||||
|  | ||||
| Note that "\xff" is stored as the byte 255, which may be invalid in some | ||||
| encodings.  Use "\u00ff" to store character 255 correctly as UTF-8. | ||||
| @@ -1211,8 +1224,8 @@ The arguments are optional.  Example: > | ||||
| 							*closure* | ||||
| Lambda expressions can access outer scope variables and arguments.  This is | ||||
| often called a closure.  Example where "i" and "a:arg" are used in a lambda | ||||
| while they exist in the function scope.  They remain valid even after the | ||||
| function returns: > | ||||
| while they already exist in the function scope.  They remain valid even after | ||||
| the function returns: > | ||||
| 	:function Foo(arg) | ||||
| 	:  let i = 3 | ||||
| 	:  return {x -> x + i - a:arg} | ||||
| @@ -1220,8 +1233,11 @@ function returns: > | ||||
| 	:let Bar = Foo(4) | ||||
| 	:echo Bar(6) | ||||
| <	5 | ||||
| See also |:func-closure|. Lambda and closure support can be checked with: > | ||||
|         if has('lambda') | ||||
| Note that the variables must exist in the outer scope before the lamba is | ||||
| defined for this to work.  See also |:func-closure|. | ||||
|  | ||||
| Lambda and closure support can be checked with: > | ||||
| 	if has('lambda') | ||||
|  | ||||
| Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > | ||||
| 	:echo map([1, 2, 3], {idx, val -> val + 1}) | ||||
| @@ -9459,9 +9475,13 @@ This does NOT work: > | ||||
| 			When the selected range of items is partly past the | ||||
| 			end of the list, items will be added. | ||||
|  | ||||
| 					*:let+=* *:let-=* *:let.=* *E734* | ||||
|                                             *:let+=* *:let-=* *:letstar=* | ||||
|                                             *:let/=* *:let%=* *:let.=* *E734* | ||||
| :let {var} += {expr1}	Like ":let {var} = {var} + {expr1}". | ||||
| :let {var} -= {expr1}	Like ":let {var} = {var} - {expr1}". | ||||
| :let {var} *= {expr1}	Like ":let {var} = {var} * {expr1}". | ||||
| :let {var} /= {expr1}	Like ":let {var} = {var} / {expr1}". | ||||
| :let {var} %= {expr1}	Like ":let {var} = {var} % {expr1}". | ||||
| :let {var} .= {expr1}	Like ":let {var} = {var} . {expr1}". | ||||
| 			These fail if {var} was not set yet and when the type | ||||
| 			of {var} and {expr1} don't fit the operator. | ||||
|   | ||||
| @@ -1442,7 +1442,11 @@ int eval_foldexpr(char_u *arg, int *cp) | ||||
|  * ":let var = expr"		assignment command. | ||||
|  * ":let var += expr"		assignment command. | ||||
|  * ":let var -= expr"		assignment command. | ||||
|  * ":let var *= expr"		assignment command. | ||||
|  * ":let var /= expr"		assignment command. | ||||
|  * ":let var %= expr"		assignment command. | ||||
|  * ":let var .= expr"		assignment command. | ||||
|  * ":let var ..= expr"		assignment command. | ||||
|  * ":let [var1, var2] = expr"	unpack list. | ||||
|  */ | ||||
| void ex_let(exarg_T *eap) | ||||
| @@ -1465,8 +1469,8 @@ void ex_let(exarg_T *eap) | ||||
|     argend--; | ||||
|   } | ||||
|   expr = skipwhite(argend); | ||||
|   if (*expr != '=' && !(vim_strchr((char_u *)"+-.", *expr) != NULL | ||||
|                         && expr[1] == '=')) { | ||||
|   if (*expr != '=' && !((vim_strchr((char_u *)"+-*/%.", *expr) != NULL | ||||
|                          && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0)) { | ||||
|     // ":let" without "=": list variables | ||||
|     if (*arg == '[') { | ||||
|       EMSG(_(e_invarg)); | ||||
| @@ -1488,8 +1492,11 @@ void ex_let(exarg_T *eap) | ||||
|     op[0] = '='; | ||||
|     op[1] = NUL; | ||||
|     if (*expr != '=') { | ||||
|       if (vim_strchr((char_u *)"+-.", *expr) != NULL) { | ||||
|         op[0] = *expr;  // +=, -=, .= | ||||
|       if (vim_strchr((char_u *)"+-*/%.", *expr) != NULL) { | ||||
|         op[0] = *expr;  // +=, -=, *=, /=, %= or .= | ||||
|         if (expr[0] == '.' && expr[1] == '.') {  // ..= | ||||
|           expr++; | ||||
|         } | ||||
|       } | ||||
|       expr = skipwhite(expr + 2); | ||||
|     } else { | ||||
| @@ -1864,7 +1871,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, | ||||
|     if (len == 0) { | ||||
|       EMSG2(_(e_invarg2), name - 1); | ||||
|     } else { | ||||
|       if (op != NULL && (*op == '+' || *op == '-')) { | ||||
|       if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { | ||||
|         EMSG2(_(e_letwrong), op); | ||||
|       } else if (endchars != NULL | ||||
|                  && vim_strchr(endchars, *skipwhite(arg)) == NULL) { | ||||
| @@ -1927,10 +1934,12 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, | ||||
|           s = NULL;  // don't set the value | ||||
|         } else { | ||||
|           if (opt_type == 1) {  // number | ||||
|             if (*op == '+') { | ||||
|               n = numval + n; | ||||
|             } else { | ||||
|               n = numval - n; | ||||
|             switch (*op) { | ||||
|               case '+': n = numval + n; break; | ||||
|               case '-': n = numval - n; break; | ||||
|               case '*': n = numval * n; break; | ||||
|               case '/': n = numval / n; break; | ||||
|               case '%': n = numval % n; break; | ||||
|             } | ||||
|           } else if (opt_type == 0 && stringval != NULL) {  // string | ||||
|             char *const oldstringval = stringval; | ||||
| @@ -1951,7 +1960,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, | ||||
|   // ":let @r = expr": Set register contents. | ||||
|   } else if (*arg == '@') { | ||||
|     arg++; | ||||
|     if (op != NULL && (*op == '+' || *op == '-')) { | ||||
|     if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { | ||||
|       emsgf(_(e_letwrong), op); | ||||
|     } else if (endchars != NULL | ||||
|                && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) { | ||||
| @@ -2350,7 +2359,8 @@ static void clear_lval(lval_T *lp) | ||||
| /* | ||||
|  * Set a variable that was parsed by get_lval() to "rettv". | ||||
|  * "endp" points to just after the parsed name. | ||||
|  * "op" is NULL, "+" for "+=", "-" for "-=", "." for ".=" or "=" for "=". | ||||
|  * "op" is NULL, "+" for "+=", "-" for "-=", "*" for "*=", "/" for "/=", | ||||
|  * "%" for "%=", "." for ".=" or "=" for "=". | ||||
|  */ | ||||
| static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, | ||||
|                          int copy, const char_u *op) | ||||
| @@ -2365,7 +2375,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, | ||||
|     if (op != NULL && *op != '=') { | ||||
|       typval_T tv; | ||||
|  | ||||
|       // handle +=, -= and .= | ||||
|       // handle +=, -=, *=, /=, %= and .= | ||||
|       di = NULL; | ||||
|       if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), | ||||
|                      &tv, &di, true, false) == OK) { | ||||
| @@ -3783,6 +3793,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) | ||||
|  *	+	number addition | ||||
|  *	-	number subtraction | ||||
|  *	.	string concatenation | ||||
|  *	..	string concatenation | ||||
|  * | ||||
|  * "arg" must point to the first non-white of the expression. | ||||
|  * "arg" is advanced to the next non-white after the recognized expression. | ||||
| @@ -3830,6 +3841,9 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate) | ||||
|     /* | ||||
|      * Get the second variable. | ||||
|      */ | ||||
|     if (op == '.' && *(*arg + 1) == '.') {  // ..string concatenation | ||||
|       (*arg)++; | ||||
|     } | ||||
|     *arg = skipwhite(*arg + 1); | ||||
|     if (eval6(arg, &var2, evaluate, op == '.') == FAIL) { | ||||
|       tv_clear(rettv); | ||||
|   | ||||
| @@ -16,7 +16,7 @@ static char *e_letwrong = N_("E734: Wrong variable type for %s="); | ||||
|  | ||||
| char *e_listidx = N_("E684: list index out of range: %" PRId64); | ||||
|  | ||||
| /// Hanle tv1 += tv2, -=, .= | ||||
| /// Hanle tv1 += tv2, -=, *=, /=,  %=, .= | ||||
| /// | ||||
| /// @param[in,out]  tv1  First operand, modified typval. | ||||
| /// @param[in]  tv2  Second operand. | ||||
| @@ -51,25 +51,31 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, | ||||
|         if (tv2->v_type == VAR_LIST) { | ||||
|           break; | ||||
|         } | ||||
|         if (*op == '+' || *op == '-') { | ||||
|           // nr += nr  or  nr -= nr | ||||
|         if (vim_strchr((char_u *)"+-*/%", *op) != NULL) { | ||||
|           // nr += nr  or  nr -= nr, nr *= nr, nr /= nr, nr %= nr | ||||
|           varnumber_T n = tv_get_number(tv1); | ||||
|           if (tv2->v_type == VAR_FLOAT) { | ||||
|             float_T f = (float_T)n; | ||||
|  | ||||
|             if (*op == '+') { | ||||
|               f += tv2->vval.v_float; | ||||
|             } else { | ||||
|               f -= tv2->vval.v_float; | ||||
|             if (*op == '%') { | ||||
|               break; | ||||
|             } | ||||
|             switch (*op) { | ||||
|               case '+': f += tv2->vval.v_float; break; | ||||
|               case '-': f -= tv2->vval.v_float; break; | ||||
|               case '*': f *= tv2->vval.v_float; break; | ||||
|               case '/': f /= tv2->vval.v_float; break; | ||||
|             } | ||||
|             tv_clear(tv1); | ||||
|             tv1->v_type = VAR_FLOAT; | ||||
|             tv1->vval.v_float = f; | ||||
|           } else { | ||||
|             if (*op == '+') { | ||||
|               n += tv_get_number(tv2); | ||||
|             } else { | ||||
|               n -= tv_get_number(tv2); | ||||
|             switch (*op) { | ||||
|               case '+': n += tv_get_number(tv2); break; | ||||
|               case '-': n -= tv_get_number(tv2); break; | ||||
|               case '*': n *= tv_get_number(tv2); break; | ||||
|               case '/': n /= tv_get_number(tv2); break; | ||||
|               case '%': n %= tv_get_number(tv2); break; | ||||
|             } | ||||
|             tv_clear(tv1); | ||||
|             tv1->v_type = VAR_NUMBER; | ||||
| @@ -92,18 +98,20 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, | ||||
|         return OK; | ||||
|       } | ||||
|       case VAR_FLOAT: { | ||||
|         if (*op == '.' || (tv2->v_type != VAR_FLOAT | ||||
|                            && tv2->v_type != VAR_NUMBER | ||||
|                            && tv2->v_type != VAR_STRING)) { | ||||
|         if (*op == '%' || *op == '.' | ||||
|             || (tv2->v_type != VAR_FLOAT | ||||
|                 && tv2->v_type != VAR_NUMBER | ||||
|                 && tv2->v_type != VAR_STRING)) { | ||||
|           break; | ||||
|         } | ||||
|         const float_T f = (tv2->v_type == VAR_FLOAT | ||||
|                            ? tv2->vval.v_float | ||||
|                            : (float_T)tv_get_number(tv2)); | ||||
|         if (*op == '+') { | ||||
|           tv1->vval.v_float += f; | ||||
|         } else { | ||||
|           tv1->vval.v_float -= f; | ||||
|         switch (*op) { | ||||
|           case '+': tv1->vval.v_float += f; break; | ||||
|           case '-': tv1->vval.v_float -= f; break; | ||||
|           case '*': tv1->vval.v_float *= f; break; | ||||
|           case '/': tv1->vval.v_float /= f; break; | ||||
|         } | ||||
|         return OK; | ||||
|       } | ||||
|   | ||||
| @@ -49,3 +49,32 @@ func Test_line_continuation() | ||||
| 	"\ and some more | ||||
|   call assert_equal([5, 6], array) | ||||
| endfunc | ||||
|  | ||||
| func Test_string_concatenation() | ||||
|   call assert_equal('ab', 'a'.'b') | ||||
|   call assert_equal('ab', 'a' .'b') | ||||
|   call assert_equal('ab', 'a'. 'b') | ||||
|   call assert_equal('ab', 'a' . 'b') | ||||
|  | ||||
|   call assert_equal('ab', 'a'..'b') | ||||
|   call assert_equal('ab', 'a' ..'b') | ||||
|   call assert_equal('ab', 'a'.. 'b') | ||||
|   call assert_equal('ab', 'a' .. 'b') | ||||
|  | ||||
|   let a = 'a' | ||||
|   let b = 'b' | ||||
|   let a .= b | ||||
|   call assert_equal('ab', a) | ||||
|  | ||||
|   let a = 'a' | ||||
|   let a.=b | ||||
|   call assert_equal('ab', a) | ||||
|  | ||||
|   let a = 'a' | ||||
|   let a ..= b | ||||
|   call assert_equal('ab', a) | ||||
|  | ||||
|   let a = 'a' | ||||
|   let a..=b | ||||
|   call assert_equal('ab', a) | ||||
| endfunc | ||||
|   | ||||
| @@ -1294,6 +1294,84 @@ func Test_script_local_func() | ||||
|   enew! | close | ||||
| endfunc | ||||
|  | ||||
| func Test_compound_assignment_operators() | ||||
|     " Test for number | ||||
|     let x = 1 | ||||
|     let x += 10 | ||||
|     call assert_equal(11, x) | ||||
|     let x -= 5 | ||||
|     call assert_equal(6, x) | ||||
|     let x *= 4 | ||||
|     call assert_equal(24, x) | ||||
|     let x /= 3 | ||||
|     call assert_equal(8, x) | ||||
|     let x %= 3 | ||||
|     call assert_equal(2, x) | ||||
|     let x .= 'n' | ||||
|     call assert_equal('2n', x) | ||||
|  | ||||
|     " Test for string | ||||
|     let x = 'str' | ||||
|     let x .= 'ing' | ||||
|     call assert_equal('string', x) | ||||
|     let x += 1 | ||||
|     call assert_equal(1, x) | ||||
|     let x -= 1.5 | ||||
|     call assert_equal(-0.5, x) | ||||
|  | ||||
|     if has('float') | ||||
|         " Test for float | ||||
|         let x = 0.5 | ||||
|         let x += 4.5 | ||||
|         call assert_equal(5.0, x) | ||||
|         let x -= 1.5 | ||||
|         call assert_equal(3.5, x) | ||||
|         let x *= 3.0 | ||||
|         call assert_equal(10.5, x) | ||||
|         let x /= 2.5 | ||||
|         call assert_equal(4.2, x) | ||||
|         call assert_fails('let x %= 0.5', 'E734') | ||||
|         call assert_fails('let x .= "f"', 'E734') | ||||
|     endif | ||||
|  | ||||
|     " Test for environment variable | ||||
|     let $FOO = 1 | ||||
|     call assert_fails('let $FOO += 1', 'E734') | ||||
|     call assert_fails('let $FOO -= 1', 'E734') | ||||
|     call assert_fails('let $FOO *= 1', 'E734') | ||||
|     call assert_fails('let $FOO /= 1', 'E734') | ||||
|     call assert_fails('let $FOO %= 1', 'E734') | ||||
|     let $FOO .= 's' | ||||
|     call assert_equal('1s', $FOO) | ||||
|     unlet $FOO | ||||
|  | ||||
|     " Test for option variable (type: number) | ||||
|     let &scrolljump = 1 | ||||
|     let &scrolljump += 5 | ||||
|     call assert_equal(6, &scrolljump) | ||||
|     let &scrolljump -= 2 | ||||
|     call assert_equal(4, &scrolljump) | ||||
|     let &scrolljump *= 3 | ||||
|     call assert_equal(12, &scrolljump) | ||||
|     let &scrolljump /= 2 | ||||
|     call assert_equal(6, &scrolljump) | ||||
|     let &scrolljump %= 5 | ||||
|     call assert_equal(1, &scrolljump) | ||||
|     call assert_fails('let &scrolljump .= "j"', 'E734') | ||||
|     set scrolljump&vim | ||||
|  | ||||
|     " Test for register | ||||
|     let @/ = 1 | ||||
|     call assert_fails('let @/ += 1', 'E734') | ||||
|     call assert_fails('let @/ -= 1', 'E734') | ||||
|     call assert_fails('let @/ *= 1', 'E734') | ||||
|     call assert_fails('let @/ /= 1', 'E734') | ||||
|     call assert_fails('let @/ %= 1', 'E734') | ||||
|     let @/ .= 's' | ||||
|     call assert_equal('1s', @/) | ||||
|     let @/ = '' | ||||
| endfunc | ||||
|  | ||||
| "------------------------------------------------------------------------------- | ||||
| " Modelines								    {{{1 | ||||
| " vim: ts=8 sw=4 tw=80 fdm=marker | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes