mirror of
https://github.com/neovim/neovim
synced 2025-07-15 16:51:49 +00:00
Merge pull request #34626 from zeertzjq/vim-74f0a77
vim-patch: :uniq Ex command
This commit is contained in:
@ -1835,6 +1835,7 @@ And a few warnings:
|
||||
|
||||
Vim has a sorting function and a sorting command. The sorting function can be
|
||||
found here: |sort()|, |uniq()|.
|
||||
Also see |:uniq|.
|
||||
|
||||
*:sor* *:sort*
|
||||
:[range]sor[t][!] [b][f][i][l][n][o][r][u][x] [/{pattern}/]
|
||||
@ -1844,7 +1845,7 @@ found here: |sort()|, |uniq()|.
|
||||
With [!] the order is reversed.
|
||||
|
||||
With [i] case is ignored.
|
||||
|
||||
*:sort-l*
|
||||
With [l] sort uses the current collation locale.
|
||||
Implementation details: strcoll() is used to compare
|
||||
strings. See |:language| to check or set the collation
|
||||
@ -1876,13 +1877,14 @@ found here: |sort()|, |uniq()|.
|
||||
|
||||
With [b] sorting is done on the first binary number in
|
||||
the line (after or inside a {pattern} match).
|
||||
|
||||
*:sort-u* *:sort-uniq*
|
||||
With [u] (u stands for unique) only keep the first of
|
||||
a sequence of identical lines (ignoring case when [i]
|
||||
is used). Without this flag, a sequence of identical
|
||||
lines will be kept in their original order.
|
||||
Note that leading and trailing white space may cause
|
||||
lines to be different.
|
||||
When you just want to make things unique, use |:uniq|.
|
||||
|
||||
When /{pattern}/ is specified and there is no [r] flag
|
||||
the text matched with {pattern} is skipped, so that
|
||||
@ -1929,4 +1931,55 @@ The sorting can be interrupted, but if you interrupt it too late in the
|
||||
process you may end up with duplicated lines. This also depends on the system
|
||||
library function used.
|
||||
|
||||
==============================================================================
|
||||
8. Deduplicating text *deduplicating* *unique*
|
||||
|
||||
Vim has a deduplicating function and a deduplicating command. The
|
||||
deduplicating function can be found here: |uniq()|.
|
||||
Also see |:sort-uniq|.
|
||||
|
||||
*:uni* *:uniq*
|
||||
:[range]uni[q][!] [i][l][r][u] [/{pattern}/]
|
||||
Remove duplicate lines that are adjacent to each other
|
||||
in [range]. When no range is given, all lines are
|
||||
processed.
|
||||
|
||||
With [i] case is ignored when comparing lines.
|
||||
|
||||
With [l] comparison uses the current collation locale.
|
||||
See |:sort-l| for more details.
|
||||
|
||||
With [r] comparison is done on the text that matches
|
||||
/{pattern}/ instead of the full line.
|
||||
|
||||
With [u] only keep lines that do not repeat (i.e., are
|
||||
not immediately followed by the same line).
|
||||
|
||||
With [!] only keep lines that are immediately followed
|
||||
by a duplicate.
|
||||
|
||||
If both [!] and [u] are given, [u] is ignored and [!]
|
||||
takes effect.
|
||||
|
||||
When /{pattern}/ is specified and [r] is not used, the
|
||||
text matched with {pattern} is skipped and comparison
|
||||
is done on what comes after the match.
|
||||
'ignorecase' applies to the pattern, but 'smartcase'
|
||||
is not used.
|
||||
Instead of the slash any non-letter can be used.
|
||||
|
||||
For example, to remove adjacent duplicate lines based
|
||||
on the second comma-separated field: >
|
||||
:uniq /[^,]*,/
|
||||
< Or to keep only unique lines ignoring the first 5
|
||||
characters: >
|
||||
:uniq u /.\{5}/
|
||||
< If {pattern} is empty (e.g. // is used), the last
|
||||
search pattern is used.
|
||||
|
||||
Note that leading and trailing white space may cause
|
||||
lines to be considered different.
|
||||
To remove all duplicates regardless of position, use
|
||||
|:sort-u| or external tools.
|
||||
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
|
@ -1666,6 +1666,7 @@ tag command action ~
|
||||
|:unabbreviate| :una[bbreviate] remove abbreviation
|
||||
|:unhide| :unh[ide] open a window for each loaded file in the
|
||||
buffer list
|
||||
|:uniq| :uni[q] uniq lines
|
||||
|:unlet| :unl[et] delete variable
|
||||
|:unlockvar| :unlo[ckvar] unlock variables
|
||||
|:unmap| :unm[ap] remove mapping
|
||||
|
@ -155,6 +155,7 @@ DIAGNOSTICS
|
||||
EDITOR
|
||||
|
||||
• |:iput| works like |:put| but adjusts indent.
|
||||
• |:uniq| deduplicates text in the current buffer.
|
||||
• |omnicompletion| in `help` buffer. |ft-help-omni|
|
||||
• Setting "'0" in 'shada' prevents storing the jumplist in the shada file.
|
||||
• 'shada' now correctly respects "/0" and "f0".
|
||||
|
@ -11653,6 +11653,7 @@ uniq({list} [, {func} [, {dict}]]) *uniq()* *E88
|
||||
let newlist = uniq(copy(mylist))
|
||||
< The default compare function uses the string representation of
|
||||
each item. For the use of {func} and {dict} see |sort()|.
|
||||
For deduplicating text in the current buffer see |:uniq|.
|
||||
|
||||
Returns zero if {list} is not a |List|.
|
||||
|
||||
|
1
runtime/lua/vim/_meta/vimfn.lua
generated
1
runtime/lua/vim/_meta/vimfn.lua
generated
@ -10612,6 +10612,7 @@ function vim.fn.undotree(buf) end
|
||||
--- let newlist = uniq(copy(mylist))
|
||||
--- <The default compare function uses the string representation of
|
||||
--- each item. For the use of {func} and {dict} see |sort()|.
|
||||
--- For deduplicating text in the current buffer see |:uniq|.
|
||||
---
|
||||
--- Returns zero if {list} is not a |List|.
|
||||
---
|
||||
|
@ -212,7 +212,7 @@ syn match vimNumber '\<0z\%(\x\x\)\+\%(\.\%(\x\x\)\+\)*' skipwhite nextgroup=@vi
|
||||
syn case match
|
||||
|
||||
" All vimCommands are contained by vimIsCommand. {{{2
|
||||
syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutocmd,vimAugroup,vimBehave,vimCall,vimCatch,vimConst,vimDoautocmd,vimDebuggreedy,vimDef,vimDefFold,vimDelcommand,vimDelFunction,@vimEcho,vimElse,vimEnddef,vimEndfunction,vimEndif,vimExecute,vimIsCommand,vimExtCmd,vimExFilter,vimExMark,vimFor,vimFunction,vimFunctionFold,vimGrep,vimGrepAdd,vimGlobal,vimHelpgrep,vimHighlight,vimImport,vimLet,vimLoadkeymap,vimLockvar,vimMake,vimMap,vimMark,vimMatch,vimNotFunc,vimNormal,vimProfdel,vimProfile,vimRedir,vimSet,vimSleep,vimSort,vimSyntax,vimThrow,vimUnlet,vimUnlockvar,vimUnmap,vimUserCmd,vimVimgrep,vimVimgrepadd,vimMenu,vimMenutranslate,@vim9CmdList,@vimExUserCmdList,vimLua,vimMzScheme,vimPerl,vimPython,vimPython3,vimPythonX,vimRuby,vimTcl
|
||||
syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutocmd,vimAugroup,vimBehave,vimCall,vimCatch,vimConst,vimDoautocmd,vimDebuggreedy,vimDef,vimDefFold,vimDelcommand,vimDelFunction,@vimEcho,vimElse,vimEnddef,vimEndfunction,vimEndif,vimExecute,vimIsCommand,vimExtCmd,vimExFilter,vimExMark,vimFor,vimFunction,vimFunctionFold,vimGrep,vimGrepAdd,vimGlobal,vimHelpgrep,vimHighlight,vimImport,vimLet,vimLoadkeymap,vimLockvar,vimMake,vimMap,vimMark,vimMatch,vimNotFunc,vimNormal,vimProfdel,vimProfile,vimRedir,vimSet,vimSleep,vimSort,vimSyntax,vimThrow,vimUniq,vimUnlet,vimUnlockvar,vimUnmap,vimUserCmd,vimVimgrep,vimVimgrepadd,vimMenu,vimMenutranslate,@vim9CmdList,@vimExUserCmdList,vimLua,vimMzScheme,vimPerl,vimPython,vimPython3,vimPythonX,vimRuby,vimTcl
|
||||
syn cluster vim9CmdList contains=vim9Abstract,vim9Class,vim9Const,vim9Enum,vim9Export,vim9Final,vim9For,vim9Interface,vim9Type,vim9Var
|
||||
syn match vimCmdSep "\\\@1<!|" skipwhite nextgroup=@vimCmdList,vimSubst1,vimFunc
|
||||
syn match vimCmdSep ":\+" skipwhite nextgroup=@vimCmdList,vimSubst1
|
||||
@ -1506,6 +1506,23 @@ syn region vimSortPattern contained
|
||||
|
||||
syn cluster vimSortOptions contains=vimSortOptions,vimSortOptionsError
|
||||
|
||||
" Uniq: {{{2
|
||||
" ====
|
||||
syn match vimUniq "\<uniq\=\>" skipwhite nextgroup=vimUniqBang,@vimUniqOptions,vimUniqPattern,vimCmdSep
|
||||
syn match vimUniqBang contained "\a\@1<=!" skipwhite nextgroup=@vimUniqOptions,vimUniqPattern,vimCmdSep
|
||||
syn match vimUniqOptionsError contained "\a\+"
|
||||
syn match vimUniqOptions contained "\<[ilur]*\>" skipwhite nextgroup=vimUniqPattern,vimCmdSep
|
||||
syn region vimUniqPattern contained
|
||||
\ matchgroup=Delimiter
|
||||
\ start="\z([^[:space:][:alpha:]|]\)"
|
||||
\ skip="\\\\\|\\\z1"
|
||||
\ end="\z1"
|
||||
\ skipwhite nextgroup=@vimUniqOptions,vimCmdSep
|
||||
\ contains=@vimSubstList
|
||||
\ oneline
|
||||
|
||||
syn cluster vimUniqOptions contains=vimUniqOptions,vimUniqOptionsError
|
||||
|
||||
" Syntax: {{{2
|
||||
"=======
|
||||
syn match vimGroupList contained "[^[:space:],]\+\%(\s*,\s*[^[:space:],]\+\)*" contains=vimGroupSpecial
|
||||
@ -2379,6 +2396,9 @@ if !exists("skip_vim_syntax_inits")
|
||||
hi def link vimThrow vimCommand
|
||||
hi def link vimTodo Todo
|
||||
hi def link vimType Type
|
||||
hi def link vimUniq vimCommand
|
||||
hi def link vimUniqBang vimBang
|
||||
hi def link vimUniqOptions Special
|
||||
hi def link vimUnlet vimCommand
|
||||
hi def link vimUnletBang vimBang
|
||||
hi def link vimUnmap vimMap
|
||||
|
@ -12835,6 +12835,7 @@ M.funcs = {
|
||||
let newlist = uniq(copy(mylist))
|
||||
<The default compare function uses the string representation of
|
||||
each item. For the use of {func} and {dict} see |sort()|.
|
||||
For deduplicating text in the current buffer see |:uniq|.
|
||||
|
||||
Returns zero if {list} is not a |List|.
|
||||
|
||||
|
@ -488,8 +488,7 @@ void ex_sort(exarg_T *eap)
|
||||
format_found++;
|
||||
} else if (*p == 'u') {
|
||||
unique = true;
|
||||
} else if (*p == '"') {
|
||||
// comment start
|
||||
} else if (*p == '"') { // comment start
|
||||
break;
|
||||
} else if (check_nextcmd(p) != NULL) {
|
||||
eap->nextcmd = check_nextcmd(p);
|
||||
@ -678,7 +677,6 @@ void ex_sort(exarg_T *eap)
|
||||
extmark_splice(curbuf, eap->line1 - 1, 0,
|
||||
(int)count, 0, old_count,
|
||||
lnum - eap->line2, 0, new_count, kExtmarkUndo);
|
||||
|
||||
changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, -deleted, true);
|
||||
}
|
||||
|
||||
@ -695,6 +693,203 @@ sortend:
|
||||
}
|
||||
}
|
||||
|
||||
/// ":uniq".
|
||||
void ex_uniq(exarg_T *eap)
|
||||
{
|
||||
regmatch_T regmatch;
|
||||
int maxlen = 0;
|
||||
linenr_T count = eap->line2 - eap->line1 + 1;
|
||||
bool keep_only_unique = false;
|
||||
bool keep_only_not_unique = eap->forceit;
|
||||
linenr_T deleted = 0;
|
||||
|
||||
// Uniq one line is really quick!
|
||||
if (count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) {
|
||||
return;
|
||||
}
|
||||
sortbuf1 = NULL;
|
||||
regmatch.regprog = NULL;
|
||||
|
||||
sort_abort = sort_ic = sort_lc = sort_rx = sort_nr = sort_flt = false;
|
||||
bool change_occurred = false; // Buffer contents changed.
|
||||
|
||||
for (char *p = eap->arg; *p != NUL; p++) {
|
||||
if (ascii_iswhite(*p)) {
|
||||
// Skip
|
||||
} else if (*p == 'i') {
|
||||
sort_ic = true;
|
||||
} else if (*p == 'l') {
|
||||
sort_lc = true;
|
||||
} else if (*p == 'r') {
|
||||
sort_rx = true;
|
||||
} else if (*p == 'u') {
|
||||
// 'u' is only valid when '!' is not given.
|
||||
if (!keep_only_not_unique) {
|
||||
keep_only_unique = true;
|
||||
}
|
||||
} else if (*p == '"') { // comment start
|
||||
break;
|
||||
} else if (eap->nextcmd == NULL && check_nextcmd(p) != NULL) {
|
||||
eap->nextcmd = check_nextcmd(p);
|
||||
break;
|
||||
} else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) {
|
||||
char *s = skip_regexp_err(p + 1, *p, true);
|
||||
if (s == NULL) {
|
||||
goto uniqend;
|
||||
}
|
||||
*s = NUL;
|
||||
// Use last search pattern if uniq pattern is empty.
|
||||
if (s == p + 1) {
|
||||
if (last_search_pat() == NULL) {
|
||||
emsg(_(e_noprevre));
|
||||
goto uniqend;
|
||||
}
|
||||
regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
|
||||
} else {
|
||||
regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC);
|
||||
}
|
||||
if (regmatch.regprog == NULL) {
|
||||
goto uniqend;
|
||||
}
|
||||
p = s; // continue after the regexp
|
||||
regmatch.rm_ic = p_ic;
|
||||
} else {
|
||||
semsg(_(e_invarg2), p);
|
||||
goto uniqend;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the length of the longest line.
|
||||
for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
|
||||
int len = ml_get_len(lnum);
|
||||
if (maxlen < len) {
|
||||
maxlen = len;
|
||||
}
|
||||
|
||||
if (got_int) {
|
||||
goto uniqend;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate a buffer that can hold the longest line.
|
||||
sortbuf1 = xmalloc((size_t)maxlen + 1);
|
||||
|
||||
// Delete lines according to options.
|
||||
bool match_continue = false;
|
||||
bool next_is_unmatch = false;
|
||||
linenr_T done_lnum = eap->line1 - 1;
|
||||
linenr_T delete_lnum = 0;
|
||||
for (linenr_T i = 0; i < count; i++) {
|
||||
linenr_T get_lnum = eap->line1 + i;
|
||||
|
||||
char *s = ml_get(get_lnum);
|
||||
int len = ml_get_len(get_lnum);
|
||||
|
||||
colnr_T start_col = 0;
|
||||
colnr_T end_col = len;
|
||||
if (regmatch.regprog != NULL && vim_regexec(®match, s, 0)) {
|
||||
if (sort_rx) {
|
||||
start_col = (colnr_T)(regmatch.startp[0] - s);
|
||||
end_col = (colnr_T)(regmatch.endp[0] - s);
|
||||
} else {
|
||||
start_col = (colnr_T)(regmatch.endp[0] - s);
|
||||
}
|
||||
} else if (regmatch.regprog != NULL) {
|
||||
end_col = 0;
|
||||
}
|
||||
char save_c = NUL; // temporary character storage
|
||||
if (end_col > 0) {
|
||||
save_c = s[end_col];
|
||||
s[end_col] = NUL;
|
||||
}
|
||||
|
||||
bool is_match = i > 0 ? !string_compare(&s[start_col], sortbuf1) : false;
|
||||
delete_lnum = 0;
|
||||
if (next_is_unmatch) {
|
||||
is_match = false;
|
||||
next_is_unmatch = false;
|
||||
}
|
||||
|
||||
if (!keep_only_unique && !keep_only_not_unique) {
|
||||
if (is_match) {
|
||||
delete_lnum = get_lnum;
|
||||
} else {
|
||||
STRCPY(sortbuf1, &s[start_col]);
|
||||
}
|
||||
} else if (keep_only_not_unique) {
|
||||
if (is_match) {
|
||||
done_lnum = get_lnum - 1;
|
||||
delete_lnum = get_lnum;
|
||||
match_continue = true;
|
||||
} else {
|
||||
if (i > 0 && !match_continue && get_lnum - 1 > done_lnum) {
|
||||
delete_lnum = get_lnum - 1;
|
||||
next_is_unmatch = true;
|
||||
} else if (i >= count - 1) {
|
||||
delete_lnum = get_lnum;
|
||||
}
|
||||
match_continue = false;
|
||||
STRCPY(sortbuf1, &s[start_col]);
|
||||
}
|
||||
} else { // keep_only_unique
|
||||
if (is_match) {
|
||||
if (!match_continue) {
|
||||
delete_lnum = get_lnum - 1;
|
||||
} else {
|
||||
delete_lnum = get_lnum;
|
||||
}
|
||||
match_continue = true;
|
||||
} else {
|
||||
if (i == 0 && match_continue) {
|
||||
delete_lnum = get_lnum;
|
||||
}
|
||||
match_continue = false;
|
||||
STRCPY(sortbuf1, &s[start_col]);
|
||||
}
|
||||
}
|
||||
|
||||
if (end_col > 0) {
|
||||
s[end_col] = save_c;
|
||||
}
|
||||
|
||||
if (delete_lnum > 0) {
|
||||
ml_delete(delete_lnum, false);
|
||||
i -= get_lnum - delete_lnum + 1;
|
||||
count--;
|
||||
deleted++;
|
||||
change_occurred = true;
|
||||
}
|
||||
|
||||
fast_breakcheck();
|
||||
if (got_int) {
|
||||
goto uniqend;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust marks for deleted lines and prepare for displaying.
|
||||
mark_adjust(eap->line2 - deleted, eap->line2, MAXLNUM, -deleted,
|
||||
change_occurred ? kExtmarkUndo : kExtmarkNOOP);
|
||||
msgmore(-deleted);
|
||||
|
||||
if (change_occurred) {
|
||||
changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, -deleted, true);
|
||||
}
|
||||
|
||||
curwin->w_cursor.lnum = eap->line1;
|
||||
beginline(BL_WHITE | BL_FIX);
|
||||
|
||||
uniqend:
|
||||
xfree(sortbuf1);
|
||||
vim_regfree(regmatch.regprog);
|
||||
if (got_int) {
|
||||
emsg(_(e_interr));
|
||||
}
|
||||
}
|
||||
|
||||
/// :move command - move lines line1-line2 to line dest
|
||||
///
|
||||
/// @return FAIL for failure, OK otherwise
|
||||
|
@ -3017,6 +3017,12 @@ M.cmds = {
|
||||
addr_type = 'ADDR_OTHER',
|
||||
func = 'ex_buffer_all',
|
||||
},
|
||||
{
|
||||
command = 'uniq',
|
||||
flags = bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, EXTRA, NOTRLCOM, MODIFY),
|
||||
addr_type = 'ADDR_LINES',
|
||||
func = 'ex_uniq',
|
||||
},
|
||||
{
|
||||
command = 'unlet',
|
||||
flags = bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN, LOCK_OK),
|
||||
|
@ -344,7 +344,8 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
|
||||
} else if (*cmd == 's' && cmd[1] == 'n') {
|
||||
magic_overruled = OPTION_MAGIC_OFF;
|
||||
}
|
||||
} else if (strncmp(cmd, "sort", (size_t)MAX(p - cmd, 3)) == 0) {
|
||||
} else if (strncmp(cmd, "sort", (size_t)MAX(p - cmd, 3)) == 0
|
||||
|| strncmp(cmd, "uniq", (size_t)MAX(p - cmd, 3)) == 0) {
|
||||
// skip over ! and flags
|
||||
if (*p == '!') {
|
||||
p = skipwhite(p + 1);
|
||||
|
612
test/old/testdir/test_uniq.vim
Normal file
612
test/old/testdir/test_uniq.vim
Normal file
@ -0,0 +1,612 @@
|
||||
" Tests for the ":uniq" command.
|
||||
|
||||
source check.vim
|
||||
|
||||
" Tests for the ":uniq" command.
|
||||
func Test_uniq_cmd()
|
||||
let tests = [
|
||||
\ {
|
||||
\ 'name' : 'Alphabetical uniq #1',
|
||||
\ 'cmd' : '%uniq',
|
||||
\ 'input' : [
|
||||
\ 'abc',
|
||||
\ 'ab',
|
||||
\ 'a',
|
||||
\ 'a321',
|
||||
\ 'a123',
|
||||
\ 'a123',
|
||||
\ 'a123',
|
||||
\ 'a123',
|
||||
\ 'a122',
|
||||
\ 'a123',
|
||||
\ 'b321',
|
||||
\ 'c123d',
|
||||
\ ' 123b',
|
||||
\ 'c321d',
|
||||
\ 'b322b',
|
||||
\ 'b321',
|
||||
\ 'b321b'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'abc',
|
||||
\ 'ab',
|
||||
\ 'a',
|
||||
\ 'a321',
|
||||
\ 'a123',
|
||||
\ 'a122',
|
||||
\ 'a123',
|
||||
\ 'b321',
|
||||
\ 'c123d',
|
||||
\ ' 123b',
|
||||
\ 'c321d',
|
||||
\ 'b322b',
|
||||
\ 'b321',
|
||||
\ 'b321b'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'Alphabetical uniq #2',
|
||||
\ 'cmd' : '%uniq',
|
||||
\ 'input' : [
|
||||
\ 'abc',
|
||||
\ 'abc',
|
||||
\ 'abc',
|
||||
\ 'ab',
|
||||
\ 'a',
|
||||
\ 'a321',
|
||||
\ 'a122',
|
||||
\ 'b321',
|
||||
\ 'a123',
|
||||
\ 'a123',
|
||||
\ 'c123d',
|
||||
\ ' 123b',
|
||||
\ 'c321d',
|
||||
\ 'b322b',
|
||||
\ 'b321',
|
||||
\ 'b321b'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'abc',
|
||||
\ 'ab',
|
||||
\ 'a',
|
||||
\ 'a321',
|
||||
\ 'a122',
|
||||
\ 'b321',
|
||||
\ 'a123',
|
||||
\ 'c123d',
|
||||
\ ' 123b',
|
||||
\ 'c321d',
|
||||
\ 'b322b',
|
||||
\ 'b321',
|
||||
\ 'b321b'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'alphabetical, uniqed input',
|
||||
\ 'cmd' : 'uniq',
|
||||
\ 'input' : [
|
||||
\ 'a',
|
||||
\ 'b',
|
||||
\ 'c',
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'a',
|
||||
\ 'b',
|
||||
\ 'c',
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'alphabetical, uniqed input, unique at end',
|
||||
\ 'cmd' : 'uniq',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'cc',
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq one line buffer',
|
||||
\ 'cmd' : 'uniq',
|
||||
\ 'input' : [
|
||||
\ 'single line'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'single line'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq ignoring case',
|
||||
\ 'cmd' : '%uniq i',
|
||||
\ 'input' : [
|
||||
\ 'BB',
|
||||
\ 'Cc',
|
||||
\ 'cc',
|
||||
\ 'Cc',
|
||||
\ 'aa'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'BB',
|
||||
\ 'Cc',
|
||||
\ 'aa'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq not uniqued #1',
|
||||
\ 'cmd' : '%uniq!',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'cc',
|
||||
\ 'yyy',
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq not uniqued #2',
|
||||
\ 'cmd' : '%uniq!',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'yyy',
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq not uniqued ("u" is ignored)',
|
||||
\ 'cmd' : '%uniq! u',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'cc',
|
||||
\ 'yyy',
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq not uniqued, ignoring case',
|
||||
\ 'cmd' : '%uniq! i',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'Cc',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'cc',
|
||||
\ 'yyy',
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq only unique #1',
|
||||
\ 'cmd' : '%uniq u',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'zz'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq only unique #2',
|
||||
\ 'cmd' : '%uniq u',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'cc',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'bb',
|
||||
\ 'zz'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq only unique, ignoring case',
|
||||
\ 'cmd' : '%uniq ui',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'Cc',
|
||||
\ 'cc',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'zz'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq on first 2 charscters',
|
||||
\ 'cmd' : '%uniq r /^../',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc1',
|
||||
\ 'cc2',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy2',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'zz'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq on after 2 charscters',
|
||||
\ 'cmd' : '%uniq /^../',
|
||||
\ 'input' : [
|
||||
\ '11aa',
|
||||
\ '11cc',
|
||||
\ '13cc',
|
||||
\ '13cc',
|
||||
\ '13bb',
|
||||
\ '13aa',
|
||||
\ '12yyy',
|
||||
\ '11yyy',
|
||||
\ '11zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ '11aa',
|
||||
\ '11cc',
|
||||
\ '13bb',
|
||||
\ '13aa',
|
||||
\ '12yyy',
|
||||
\ '11zz'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq on first 2 charscters, not uniqued',
|
||||
\ 'cmd' : '%uniq! r /^../',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc1',
|
||||
\ 'cc2',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy2',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'cc',
|
||||
\ 'yyy'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq on after 2 charscters, not uniqued',
|
||||
\ 'cmd' : '%uniq! /^../',
|
||||
\ 'input' : [
|
||||
\ '11aa',
|
||||
\ '11cc',
|
||||
\ '13cc',
|
||||
\ '13cc',
|
||||
\ '13bb',
|
||||
\ '13aa',
|
||||
\ '12yyy',
|
||||
\ '11yyy',
|
||||
\ '11zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ '11cc',
|
||||
\ '12yyy'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq on first 2 charscters, only unique',
|
||||
\ 'cmd' : '%uniq ru /^../',
|
||||
\ 'input' : [
|
||||
\ 'aa',
|
||||
\ 'cc',
|
||||
\ 'cc1',
|
||||
\ 'cc2',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'yyy',
|
||||
\ 'yyy2',
|
||||
\ 'zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'aa',
|
||||
\ 'bb',
|
||||
\ 'aa',
|
||||
\ 'zz'
|
||||
\ ]
|
||||
\ },
|
||||
\ {
|
||||
\ 'name' : 'uniq on after 2 charscters, only unique',
|
||||
\ 'cmd' : '%uniq u /^../',
|
||||
\ 'input' : [
|
||||
\ '11aa',
|
||||
\ '11cc',
|
||||
\ '13cc',
|
||||
\ '13cc',
|
||||
\ '13bb',
|
||||
\ '13aa',
|
||||
\ '12yyy',
|
||||
\ '11yyy',
|
||||
\ '11zz'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ '11aa',
|
||||
\ '13bb',
|
||||
\ '13aa',
|
||||
\ '11zz'
|
||||
\ ]
|
||||
\ }
|
||||
\ ]
|
||||
|
||||
" This does not appear to work correctly on Mac.
|
||||
if !has('mac')
|
||||
if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
|
||||
" en_CA.utf-8 uniqs capitals before lower case
|
||||
" 'Œ' is omitted because it can uniq before or after 'œ'
|
||||
let tests += [
|
||||
\ {
|
||||
\ 'name' : 'uniq with locale ' .. v:collate,
|
||||
\ 'cmd' : '%uniq l',
|
||||
\ 'input' : [
|
||||
\ 'A',
|
||||
\ 'a',
|
||||
\ 'À',
|
||||
\ 'à',
|
||||
\ 'E',
|
||||
\ 'e',
|
||||
\ 'É',
|
||||
\ 'é',
|
||||
\ 'È',
|
||||
\ 'è',
|
||||
\ 'O',
|
||||
\ 'o',
|
||||
\ 'Ô',
|
||||
\ 'ô',
|
||||
\ 'œ',
|
||||
\ 'Z',
|
||||
\ 'z'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'A',
|
||||
\ 'a',
|
||||
\ 'À',
|
||||
\ 'à',
|
||||
\ 'E',
|
||||
\ 'e',
|
||||
\ 'É',
|
||||
\ 'é',
|
||||
\ 'È',
|
||||
\ 'è',
|
||||
\ 'O',
|
||||
\ 'o',
|
||||
\ 'Ô',
|
||||
\ 'ô',
|
||||
\ 'œ',
|
||||
\ 'Z',
|
||||
\ 'z'
|
||||
\ ]
|
||||
\ },
|
||||
\ ]
|
||||
elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
|
||||
" With these locales, the accentuated letters are ordered
|
||||
" similarly to the non-accentuated letters.
|
||||
let tests += [
|
||||
\ {
|
||||
\ 'name' : 'uniq with locale ' .. v:collate,
|
||||
\ 'cmd' : '%uniq li',
|
||||
\ 'input' : [
|
||||
\ 'A',
|
||||
\ 'À',
|
||||
\ 'a',
|
||||
\ 'à',
|
||||
\ 'à',
|
||||
\ 'E',
|
||||
\ 'È',
|
||||
\ 'É',
|
||||
\ 'o',
|
||||
\ 'O',
|
||||
\ 'Ô',
|
||||
\ 'e',
|
||||
\ 'è',
|
||||
\ 'é',
|
||||
\ 'ô',
|
||||
\ 'Œ',
|
||||
\ 'œ',
|
||||
\ 'z',
|
||||
\ 'Z'
|
||||
\ ],
|
||||
\ 'expected' : [
|
||||
\ 'A',
|
||||
\ 'À',
|
||||
\ 'a',
|
||||
\ 'à',
|
||||
\ 'E',
|
||||
\ 'È',
|
||||
\ 'É',
|
||||
\ 'o',
|
||||
\ 'O',
|
||||
\ 'Ô',
|
||||
\ 'e',
|
||||
\ 'è',
|
||||
\ 'é',
|
||||
\ 'ô',
|
||||
\ 'Œ',
|
||||
\ 'œ',
|
||||
\ 'z',
|
||||
\ 'Z'
|
||||
\ ]
|
||||
\ },
|
||||
\ ]
|
||||
endif
|
||||
endif
|
||||
|
||||
for t in tests
|
||||
enew!
|
||||
call append(0, t.input)
|
||||
$delete _
|
||||
setlocal nomodified
|
||||
execute t.cmd
|
||||
|
||||
call assert_equal(t.expected, getline(1, '$'), t.name)
|
||||
|
||||
" Previously, the ":uniq" command would set 'modified' even if the buffer
|
||||
" contents did not change. Here, we check that this problem is fixed.
|
||||
if t.input == t.expected
|
||||
call assert_false(&modified, t.name . ': &mod is not correct')
|
||||
else
|
||||
call assert_true(&modified, t.name . ': &mod is not correct')
|
||||
endif
|
||||
endfor
|
||||
|
||||
" Needs at least two lines for this test
|
||||
call setline(1, ['line1', 'line2'])
|
||||
call assert_fails('uniq no', 'E475:')
|
||||
call assert_fails('uniq c', 'E475:')
|
||||
call assert_fails('uniq #pat%', 'E654:')
|
||||
call assert_fails('uniq /\%(/', 'E53:')
|
||||
call assert_fails('333uniq', 'E16:')
|
||||
call assert_fails('1,999uniq', 'E16:')
|
||||
|
||||
enew!
|
||||
endfunc
|
||||
|
||||
func Test_uniq_cmd_report()
|
||||
enew!
|
||||
call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
|
||||
$delete _
|
||||
setlocal nomodified
|
||||
let res = execute('%uniq')
|
||||
|
||||
call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
|
||||
call assert_match("6 fewer lines", res)
|
||||
enew!
|
||||
call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
|
||||
$delete _
|
||||
setlocal nomodified report=10
|
||||
let res = execute('%uniq')
|
||||
|
||||
call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
|
||||
call assert_equal("", res)
|
||||
enew!
|
||||
call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
|
||||
$delete _
|
||||
setl report&vim
|
||||
setlocal nomodified
|
||||
let res = execute('1g/^/%uniq')
|
||||
|
||||
call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
|
||||
" the output comes from the :g command, not from the :uniq
|
||||
call assert_match("6 fewer lines", res)
|
||||
enew!
|
||||
endfunc
|
||||
|
||||
" Test for a :uniq command followed by another command
|
||||
func Test_uniq_followed_by_cmd()
|
||||
new
|
||||
let var = ''
|
||||
call setline(1, ['cc', 'aa', 'bb'])
|
||||
%uniq | let var = "uniqcmdtest"
|
||||
call assert_equal(var, "uniqcmdtest")
|
||||
call assert_equal(['cc', 'aa', 'bb'], getline(1, '$'))
|
||||
" Test for :uniq followed by a comment
|
||||
call setline(1, ['3b', '3b', '3b', '1c', '2a'])
|
||||
%uniq " uniq alphabetically
|
||||
call assert_equal(['3b', '1c', '2a'], getline(1, '$'))
|
||||
bw!
|
||||
endfunc
|
||||
|
||||
" Test for retaining marks across a :uniq
|
||||
func Test_uniq_with_marks()
|
||||
new
|
||||
call setline(1, ['cc', 'cc', 'aa', 'bb', 'bb', 'bb', 'bb'])
|
||||
call setpos("'c", [0, 1, 0, 0])
|
||||
call setpos("'a", [0, 4, 0, 0])
|
||||
call setpos("'b", [0, 7, 0, 0])
|
||||
%uniq
|
||||
call assert_equal(['cc', 'aa', 'bb'], getline(1, '$'))
|
||||
call assert_equal(1, line("'c"))
|
||||
call assert_equal(0, line("'a"))
|
||||
call assert_equal(0, line("'b"))
|
||||
bw!
|
||||
endfunc
|
||||
|
||||
" Test for undo after a :uniq
|
||||
func Test_uniq_undo()
|
||||
new
|
||||
let li = ['cc', 'cc', 'aa', 'bb', 'bb', 'bb', 'bb', 'aa']
|
||||
call writefile(li, 'XfileUniq', 'D')
|
||||
edit XfileUniq
|
||||
uniq
|
||||
call assert_equal(['cc', 'aa', 'bb', 'aa'], getline(1, '$'))
|
||||
call assert_true(&modified)
|
||||
undo
|
||||
call assert_equal(li, getline(1, '$'))
|
||||
call assert_false(&modified)
|
||||
bw!
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
Reference in New Issue
Block a user