String.vim 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. " ___vital___
  2. " NOTE: lines between '" ___vital___' is generated by :Vitalize.
  3. " Do not mofidify the code nor insert new lines before '" ___vital___'
  4. if v:version > 703 || v:version == 703 && has('patch1170')
  5. function! vital#_neocomplete#Data#String#import() abort
  6. return map({'starts_with': '', 'split3': '', 'replace_first': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'justify_equal_spacing': '', 'nr2hex': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', '_vital_created': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'dstring': '', 'pad_both_sides': '', 'substitute_last': '', 'pad_right': '', 'remove_ansi_sequences': '', '_vital_loaded': ''}, 'function("s:" . v:key)')
  7. endfunction
  8. else
  9. function! s:_SID() abort
  10. return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
  11. endfunction
  12. execute join(['function! vital#_neocomplete#Data#String#import() abort', printf("return map({'starts_with': '', 'split3': '', 'replace_first': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'justify_equal_spacing': '', 'nr2hex': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', '_vital_created': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'dstring': '', 'pad_both_sides': '', 'substitute_last': '', 'pad_right': '', 'remove_ansi_sequences': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
  13. delfunction s:_SID
  14. endif
  15. " ___vital___
  16. " Utilities for string.
  17. let s:save_cpo = &cpo
  18. set cpo&vim
  19. function! s:_vital_loaded(V) abort
  20. let s:V = a:V
  21. let s:L = s:V.import('Data.List')
  22. endfunction
  23. function! s:_vital_depends() abort
  24. return ['Data.List']
  25. endfunction
  26. function! s:_vital_created(module) abort
  27. " Expose script-local funcref
  28. if exists('s:strchars')
  29. let a:module.strchars = s:strchars
  30. endif
  31. if exists('s:wcswidth')
  32. let a:module.wcswidth = s:wcswidth
  33. endif
  34. endfunction
  35. " Substitute a:from => a:to by string.
  36. " To substitute by pattern, use substitute() instead.
  37. function! s:replace(str, from, to) abort
  38. return s:_replace(a:str, a:from, a:to, 'g')
  39. endfunction
  40. " Substitute a:from => a:to only once.
  41. " cf. s:replace()
  42. function! s:replace_first(str, from, to) abort
  43. return s:_replace(a:str, a:from, a:to, '')
  44. endfunction
  45. " implement of replace() and replace_first()
  46. function! s:_replace(str, from, to, flags) abort
  47. return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags)
  48. endfunction
  49. function! s:scan(str, pattern) abort
  50. let list = []
  51. call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g')
  52. return list
  53. endfunction
  54. function! s:reverse(str) abort
  55. return join(reverse(split(a:str, '.\zs')), '')
  56. endfunction
  57. function! s:starts_with(str, prefix) abort
  58. return stridx(a:str, a:prefix) == 0
  59. endfunction
  60. function! s:ends_with(str, suffix) abort
  61. let idx = strridx(a:str, a:suffix)
  62. return 0 <= idx && idx + len(a:suffix) == len(a:str)
  63. endfunction
  64. function! s:common_head(strs) abort
  65. if empty(a:strs)
  66. return ''
  67. endif
  68. let len = len(a:strs)
  69. if len == 1
  70. return a:strs[0]
  71. endif
  72. let strs = len == 2 ? a:strs : sort(copy(a:strs))
  73. let pat = substitute(strs[0], '.', '\="[" . escape(submatch(0), "^\\") . "]"', 'g')
  74. return pat ==# '' ? '' : matchstr(strs[-1], '\C^\%[' . pat . ']')
  75. endfunction
  76. " Split to two elements of List. ([left, right])
  77. " e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache']
  78. function! s:split_leftright(expr, pattern) abort
  79. let [left, _, right] = s:split3(a:expr, a:pattern)
  80. return [left, right]
  81. endfunction
  82. function! s:split3(expr, pattern) abort
  83. let ERROR = ['', '', '']
  84. if a:expr ==# '' || a:pattern ==# ''
  85. return ERROR
  86. endif
  87. let begin = match(a:expr, a:pattern)
  88. if begin is -1
  89. return ERROR
  90. endif
  91. let end = matchend(a:expr, a:pattern)
  92. let left = begin <=# 0 ? '' : a:expr[: begin - 1]
  93. let right = a:expr[end :]
  94. return [left, a:expr[begin : end-1], right]
  95. endfunction
  96. " Slices into strings determines the number of substrings.
  97. " e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache']
  98. function! s:nsplit(expr, n, ...) abort
  99. let pattern = get(a:000, 0, '\s')
  100. let keepempty = get(a:000, 1, 1)
  101. let ret = []
  102. let expr = a:expr
  103. if a:n <= 1
  104. return [expr]
  105. endif
  106. while 1
  107. let pos = match(expr, pattern)
  108. if pos == -1
  109. if expr !~ pattern || keepempty
  110. call add(ret, expr)
  111. endif
  112. break
  113. elseif pos >= 0
  114. let left = pos > 0 ? expr[:pos-1] : ''
  115. if pos > 0 || keepempty
  116. call add(ret, left)
  117. endif
  118. let ml = len(matchstr(expr, pattern))
  119. if pos == 0 && ml == 0
  120. let pos = 1
  121. endif
  122. let expr = expr[pos+ml :]
  123. endif
  124. if len(expr) == 0
  125. break
  126. endif
  127. if len(ret) == a:n - 1
  128. call add(ret, expr)
  129. break
  130. endif
  131. endwhile
  132. return ret
  133. endfunction
  134. " Returns the number of character in a:str.
  135. " NOTE: This returns proper value
  136. " even if a:str contains multibyte character(s).
  137. " s:strchars(str) {{{
  138. if exists('*strchars')
  139. let s:strchars = function('strchars')
  140. else
  141. function! s:strchars(str) abort
  142. return strlen(substitute(copy(a:str), '.', 'x', 'g'))
  143. endfunction
  144. endif "}}}
  145. " Returns the bool of contains any multibyte character in s:str
  146. function! s:contains_multibyte(str) abort "{{{
  147. return strlen(a:str) != s:strchars(a:str)
  148. endfunction "}}}
  149. " Remove last character from a:str.
  150. " NOTE: This returns proper value
  151. " even if a:str contains multibyte character(s).
  152. function! s:chop(str) abort "{{{
  153. return substitute(a:str, '.$', '', '')
  154. endfunction "}}}
  155. " Remove last \r,\n,\r\n from a:str.
  156. function! s:chomp(str) abort "{{{
  157. return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '')
  158. endfunction "}}}
  159. " wrap() and its internal functions
  160. " * _split_by_wcswidth_once()
  161. " * _split_by_wcswidth()
  162. " * _concat()
  163. " * wrap()
  164. "
  165. " NOTE _concat() is just a copy of Data.List.concat().
  166. " FIXME don't repeat yourself
  167. function! s:_split_by_wcswidth_once(body, x) abort
  168. let fst = s:strwidthpart(a:body, a:x)
  169. let snd = s:strwidthpart_reverse(a:body, s:wcswidth(a:body) - s:wcswidth(fst))
  170. return [fst, snd]
  171. endfunction
  172. function! s:_split_by_wcswidth(body, x) abort
  173. let memo = []
  174. let body = a:body
  175. while s:wcswidth(body) > a:x
  176. let [tmp, body] = s:_split_by_wcswidth_once(body, a:x)
  177. call add(memo, tmp)
  178. endwhile
  179. call add(memo, body)
  180. return memo
  181. endfunction
  182. function! s:trim(str) abort
  183. return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$')
  184. endfunction
  185. function! s:trim_start(str) abort
  186. return matchstr(a:str,'^\s*\zs.\{-}$')
  187. endfunction
  188. function! s:trim_end(str) abort
  189. return matchstr(a:str,'^.\{-}\ze\s*$')
  190. endfunction
  191. function! s:wrap(str,...) abort
  192. let _columns = a:0 > 0 ? a:1 : &columns
  193. return s:L.concat(
  194. \ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)'))
  195. endfunction
  196. function! s:nr2byte(nr) abort
  197. if a:nr < 0x80
  198. return nr2char(a:nr)
  199. elseif a:nr < 0x800
  200. return nr2char(a:nr/64+192).nr2char(a:nr%64+128)
  201. else
  202. return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
  203. endif
  204. endfunction
  205. function! s:nr2enc_char(charcode) abort
  206. if &encoding ==# 'utf-8'
  207. return nr2char(a:charcode)
  208. endif
  209. let char = s:nr2byte(a:charcode)
  210. if strlen(char) > 1
  211. let char = strtrans(iconv(char, 'utf-8', &encoding))
  212. endif
  213. return char
  214. endfunction
  215. function! s:nr2hex(nr) abort
  216. let n = a:nr
  217. let r = ''
  218. while n
  219. let r = '0123456789ABCDEF'[n % 16] . r
  220. let n = n / 16
  221. endwhile
  222. return r
  223. endfunction
  224. " If a ==# b, returns -1.
  225. " If a !=# b, returns first index of different character.
  226. function! s:diffidx(a, b) abort
  227. return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b]))
  228. endfunction
  229. function! s:substitute_last(expr, pat, sub) abort
  230. return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '')
  231. endfunction
  232. function! s:dstring(expr) abort
  233. let x = substitute(string(a:expr), "^'\\|'$", '', 'g')
  234. let x = substitute(x, "''", "'", 'g')
  235. return printf('"%s"', escape(x, '"'))
  236. endfunction
  237. function! s:lines(str) abort
  238. return split(a:str, '\r\?\n')
  239. endfunction
  240. function! s:_pad_with_char(str, left, right, char) abort
  241. return repeat(a:char, a:left). a:str. repeat(a:char, a:right)
  242. endfunction
  243. function! s:pad_left(str, width, ...) abort
  244. let char = get(a:, 1, ' ')
  245. if strdisplaywidth(char) != 1
  246. throw "vital: Data.String: Can't use non-half-width characters for padding."
  247. endif
  248. let left = max([0, a:width - strdisplaywidth(a:str)])
  249. return s:_pad_with_char(a:str, left, 0, char)
  250. endfunction
  251. function! s:pad_right(str, width, ...) abort
  252. let char = get(a:, 1, ' ')
  253. if strdisplaywidth(char) != 1
  254. throw "vital: Data.String: Can't use non-half-width characters for padding."
  255. endif
  256. let right = max([0, a:width - strdisplaywidth(a:str)])
  257. return s:_pad_with_char(a:str, 0, right, char)
  258. endfunction
  259. function! s:pad_both_sides(str, width, ...) abort
  260. let char = get(a:, 1, ' ')
  261. if strdisplaywidth(char) != 1
  262. throw "vital: Data.String: Can't use non-half-width characters for padding."
  263. endif
  264. let space = max([0, a:width - strdisplaywidth(a:str)])
  265. let left = space / 2
  266. let right = space - left
  267. return s:_pad_with_char(a:str, left, right, char)
  268. endfunction
  269. function! s:pad_between_letters(str, width, ...) abort
  270. let char = get(a:, 1, ' ')
  271. if strdisplaywidth(char) != 1
  272. throw "vital: Data.String: Can't use non-half-width characters for padding."
  273. endif
  274. let letters = split(a:str, '\zs')
  275. let each_width = a:width / len(letters)
  276. let str = join(map(letters, 's:pad_both_sides(v:val, each_width, char)'), '')
  277. if a:width - strdisplaywidth(str) > 0
  278. return char. s:pad_both_sides(str, a:width - 1, char)
  279. endif
  280. return str
  281. endfunction
  282. function! s:justify_equal_spacing(str, width, ...) abort
  283. let char = get(a:, 1, ' ')
  284. if strdisplaywidth(char) != 1
  285. throw "vital: Data.String: Can't use non-half-width characters for padding."
  286. endif
  287. let letters = split(a:str, '\zs')
  288. let first_letter = letters[0]
  289. " {width w/o the first letter} / {length w/o the first letter}
  290. let each_width = (a:width - strdisplaywidth(first_letter)) / (len(letters) - 1)
  291. let remainder = (a:width - strdisplaywidth(first_letter)) % (len(letters) - 1)
  292. return first_letter. join(s:L.concat([
  293. \ map(letters[1:remainder], 's:pad_left(v:val, each_width + 1, char)'),
  294. \ map(letters[remainder + 1:], 's:pad_left(v:val, each_width, char)')
  295. \ ]), '')
  296. endfunction
  297. function! s:levenshtein_distance(str1, str2) abort
  298. let letters1 = split(a:str1, '\zs')
  299. let letters2 = split(a:str2, '\zs')
  300. let length1 = len(letters1)
  301. let length2 = len(letters2)
  302. let distances = map(range(1, length1 + 1), 'map(range(1, length2 + 1), ''0'')')
  303. for i1 in range(0, length1)
  304. let distances[i1][0] = i1
  305. endfor
  306. for i2 in range(0, length2)
  307. let distances[0][i2] = i2
  308. endfor
  309. for i1 in range(1, length1)
  310. for i2 in range(1, length2)
  311. let cost = (letters1[i1 - 1] ==# letters2[i2 - 1]) ? 0 : 1
  312. let distances[i1][i2] = min([
  313. \ distances[i1 - 1][i2 ] + 1,
  314. \ distances[i1 ][i2 - 1] + 1,
  315. \ distances[i1 - 1][i2 - 1] + cost,
  316. \])
  317. endfor
  318. endfor
  319. return distances[length1][length2]
  320. endfunction
  321. function! s:padding_by_displaywidth(expr, width, float) abort
  322. let padding_char = ' '
  323. let n = a:width - strdisplaywidth(a:expr)
  324. if n <= 0
  325. let n = 0
  326. endif
  327. if a:float < 0
  328. return a:expr . repeat(padding_char, n)
  329. elseif 0 < a:float
  330. return repeat(padding_char, n) . a:expr
  331. else
  332. if n % 2 is 0
  333. return repeat(padding_char, n / 2) . a:expr . repeat(padding_char, n / 2)
  334. else
  335. return repeat(padding_char, (n - 1) / 2) . a:expr . repeat(padding_char, (n - 1) / 2) . padding_char
  336. endif
  337. endif
  338. endfunction
  339. function! s:split_by_displaywidth(expr, width, float, is_wrap) abort
  340. if a:width is 0
  341. return ['']
  342. endif
  343. let lines = []
  344. let cs = split(a:expr, '\zs')
  345. let cs_index = 0
  346. let text = ''
  347. while cs_index < len(cs)
  348. if cs[cs_index] is# "\n"
  349. let text = s:padding_by_displaywidth(text, a:width, a:float)
  350. let lines += [text]
  351. let text = ''
  352. else
  353. let w = strdisplaywidth(text . cs[cs_index])
  354. if w < a:width
  355. let text .= cs[cs_index]
  356. elseif a:width < w
  357. let text = s:padding_by_displaywidth(text, a:width, a:float)
  358. else
  359. let text .= cs[cs_index]
  360. endif
  361. if a:width <= w
  362. let lines += [text]
  363. let text = ''
  364. if a:is_wrap
  365. if a:width < w
  366. if a:width < strdisplaywidth(cs[cs_index])
  367. while get(cs, cs_index, "\n") isnot# "\n"
  368. let cs_index += 1
  369. endwhile
  370. continue
  371. else
  372. let text = cs[cs_index]
  373. endif
  374. endif
  375. else
  376. while get(cs, cs_index, "\n") isnot# "\n"
  377. let cs_index += 1
  378. endwhile
  379. continue
  380. endif
  381. endif
  382. endif
  383. let cs_index += 1
  384. endwhile
  385. if !empty(text)
  386. let lines += [ s:padding_by_displaywidth(text, a:width, a:float) ]
  387. endif
  388. return lines
  389. endfunction
  390. function! s:hash(str) abort
  391. if exists('*sha256')
  392. return sha256(a:str)
  393. else
  394. " This gives up sha256ing but just adds up char with index.
  395. let sum = 0
  396. for i in range(len(a:str))
  397. let sum += char2nr(a:str[i]) * (i + 1)
  398. endfor
  399. return printf('%x', sum)
  400. endif
  401. endfunction
  402. function! s:truncate(str, width) abort
  403. " Original function is from mattn.
  404. " http://github.com/mattn/googlereader-vim/tree/master
  405. if a:str =~# '^[\x00-\x7f]*$'
  406. return len(a:str) < a:width
  407. \ ? printf('%-' . a:width . 's', a:str)
  408. \ : strpart(a:str, 0, a:width)
  409. endif
  410. let ret = a:str
  411. let width = s:wcswidth(a:str)
  412. if width > a:width
  413. let ret = s:strwidthpart(ret, a:width)
  414. let width = s:wcswidth(ret)
  415. endif
  416. if width < a:width
  417. let ret .= repeat(' ', a:width - width)
  418. endif
  419. return ret
  420. endfunction
  421. function! s:truncate_skipping(str, max, footer_width, separator) abort
  422. let width = s:wcswidth(a:str)
  423. if width <= a:max
  424. let ret = a:str
  425. else
  426. let header_width = a:max - s:wcswidth(a:separator) - a:footer_width
  427. let ret = s:strwidthpart(a:str, header_width) . a:separator
  428. \ . s:strwidthpart_reverse(a:str, a:footer_width)
  429. endif
  430. return s:truncate(ret, a:max)
  431. endfunction
  432. function! s:strwidthpart(str, width) abort
  433. let str = tr(a:str, "\t", ' ')
  434. let vcol = a:width + 2
  435. return matchstr(str, '.*\%<' . (vcol < 0 ? 0 : vcol) . 'v')
  436. endfunction
  437. function! s:strwidthpart_reverse(str, width) abort
  438. let str = tr(a:str, "\t", ' ')
  439. let vcol = s:wcswidth(str) - a:width
  440. return matchstr(str, '\%>' . (vcol < 0 ? 0 : vcol) . 'v.*')
  441. endfunction
  442. if v:version >= 703
  443. " Use builtin function.
  444. let s:wcswidth = function('strwidth')
  445. else
  446. function! s:wcswidth(str) abort
  447. if a:str =~# '^[\x00-\x7f]*$'
  448. return strlen(a:str)
  449. endif
  450. let mx_first = '^\(.\)'
  451. let str = a:str
  452. let width = 0
  453. while 1
  454. let ucs = char2nr(substitute(str, mx_first, '\1', ''))
  455. if ucs == 0
  456. break
  457. endif
  458. let width += s:_wcwidth(ucs)
  459. let str = substitute(str, mx_first, '', '')
  460. endwhile
  461. return width
  462. endfunction
  463. " UTF-8 only.
  464. function! s:_wcwidth(ucs) abort
  465. let ucs = a:ucs
  466. if (ucs >= 0x1100
  467. \ && (ucs <= 0x115f
  468. \ || ucs == 0x2329
  469. \ || ucs == 0x232a
  470. \ || (ucs >= 0x2e80 && ucs <= 0xa4cf
  471. \ && ucs != 0x303f)
  472. \ || (ucs >= 0xac00 && ucs <= 0xd7a3)
  473. \ || (ucs >= 0xf900 && ucs <= 0xfaff)
  474. \ || (ucs >= 0xfe30 && ucs <= 0xfe6f)
  475. \ || (ucs >= 0xff00 && ucs <= 0xff60)
  476. \ || (ucs >= 0xffe0 && ucs <= 0xffe6)
  477. \ || (ucs >= 0x20000 && ucs <= 0x2fffd)
  478. \ || (ucs >= 0x30000 && ucs <= 0x3fffd)
  479. \ ))
  480. return 2
  481. endif
  482. return 1
  483. endfunction
  484. endif
  485. function! s:remove_ansi_sequences(text) abort
  486. return substitute(a:text, '\e\[\%(\%(\d\+;\)*\d\+\)\?[mK]', '', 'g')
  487. endfunction
  488. function! s:escape_pattern(str) abort
  489. " escape characters for no-magic
  490. return escape(a:str, '^$~.*[]\')
  491. endfunction
  492. function! s:unescape_pattern(str) abort
  493. " unescape characters for no-magic
  494. return s:unescape(a:str, '^$~.*[]\')
  495. endfunction
  496. function! s:unescape(str, chars) abort
  497. let chars = map(split(a:chars, '\zs'), 'escape(v:val, ''^$~.*[]\'')')
  498. return substitute(a:str, '\\\(' . join(chars, '\|') . '\)', '\1', 'g')
  499. endfunction
  500. function! s:iconv(expr, from, to) abort
  501. if a:from ==# '' || a:to ==# '' || a:from ==? a:to
  502. return a:expr
  503. endif
  504. let result = iconv(a:expr, a:from, a:to)
  505. return empty(result) ? a:expr : result
  506. endfunction
  507. " NOTE:
  508. " A definition of a TEXT file is "A file that contains characters organized
  509. " into one or more lines."
  510. " A definition of a LINE is "A sequence of zero or more non- <newline>s
  511. " plus a terminating <newline>"
  512. " That's why {stdin} always ends with <newline> ideally. However, there are
  513. " some programs which does not follow the POSIX rule and a Vim's way to join
  514. " List into TEXT; join({text}, "\n"); does not add <newline> to the end of
  515. " the last line.
  516. " That's why add a trailing <newline> if it does not exist.
  517. " REF:
  518. " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392
  519. " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205
  520. " :help split()
  521. " NOTE:
  522. " it does nothing if the text is a correct POSIX text
  523. function! s:repair_posix_text(text, ...) abort
  524. let newline = get(a:000, 0, "\n")
  525. return a:text =~# '\n$' ? a:text : a:text . newline
  526. endfunction
  527. " NOTE:
  528. " A definition of a TEXT file is "A file that contains characters organized
  529. " into one or more lines."
  530. " A definition of a LINE is "A sequence of zero or more non- <newline>s
  531. " plus a terminating <newline>"
  532. " REF:
  533. " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392
  534. " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205
  535. function! s:join_posix_lines(lines, ...) abort
  536. let newline = get(a:000, 0, "\n")
  537. return join(a:lines, newline) . newline
  538. endfunction
  539. " NOTE:
  540. " A definition of a TEXT file is "A file that contains characters organized
  541. " into one or more lines."
  542. " A definition of a LINE is "A sequence of zero or more non- <newline>s
  543. " plus a terminating <newline>"
  544. " TEXT into List; split({text}, '\r\?\n', 1); add an extra empty line at the
  545. " end of List because the end of TEXT ends with <newline> and keepempty=1 is
  546. " specified. (btw. keepempty=0 cannot be used because it will remove
  547. " emptylines in the head and the tail).
  548. " That's why removing a trailing <newline> before proceeding to 'split' is required
  549. " REF:
  550. " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392
  551. " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205
  552. function! s:split_posix_text(text, ...) abort
  553. let newline = get(a:000, 0, '\r\?\n')
  554. let text = substitute(a:text, newline . '$', '', '')
  555. return split(text, newline, 1)
  556. endfunction
  557. let &cpo = s:save_cpo
  558. unlet s:save_cpo
  559. " vim:set et ts=2 sts=2 sw=2 tw=0: