From 950a12a0f99f0fa12e0e9041174e494bbd4c8dca Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Thu, 25 May 2017 00:00:00 +0200 Subject: [PATCH] --- autoload/ingo/str/restricted.vim | 78 +++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/autoload/ingo/str/restricted.vim b/autoload/ingo/str/restricted.vim index 6fcac57..9f94015 100644 --- a/autoload/ingo/str/restricted.vim +++ b/autoload/ingo/str/restricted.vim @@ -3,7 +3,7 @@ " DEPENDENCIES: " - ingo/compat.vim autoload script " -" Copyright: (C) 2014 Ingo Karkat +" Copyright: (C) 2014-2017 Ingo Karkat " The VIM LICENSE applies to this script; see ':help copyright'. " " Maintainer: Ingo Karkat @@ -35,4 +35,80 @@ function! ingo#str#restricted#ToShortCharacterwise( expr, ... ) return (a:expr =~# '\n' || ingo#compat#strchars(a:expr) > l:maxCharacterNum ? l:default : a:expr) endfunction +function! ingo#str#restricted#ToSafeIdentifier( expr, ...) +"****************************************************************************** +"* PURPOSE: +" Restrict an arbitrary string a:expr to a short one that can be safely used +" in filenames, URLs, etc. without having to worry about quoting or escaping +" of special characters. +"* ASSUMPTIONS / PRECONDITIONS: +" None. +"* EFFECTS / POSTCONDITIONS: +" None. +"* INPUTS: +" a:expr Source text, or List of strings. +" a:options.replacementForSpecialCharacters Replacement character; default "-". +" a:options.removeFrom Which position has the lowest priority in case the +" result is still too long, and is dropped. One of +" "l", "m", "r"; default is "m", dropping from the +" middle. +" a:options.maxCharacterNum Maximum width. Defaults to 'textwidth' / 80 +" screen cells. +"* RETURN VALUES: +" Non-alphanumeric characters are replaced by +" a:options.replacementForSpecialCharacters (two between different List items); those +" at the front and end are dropped. If the text exceeds a:maxCharacterNum, +" List elements / alphanumeric sequences from the middle are dropped until it +" fits. +"****************************************************************************** + let l:options = (a:0 ? a:1 : {}) + let l:repl = get(l:options, 'replacementForSpecialCharacters', '-') + let l:removeFrom = get(l:options, 'removeFrom', 'm') + let l:maxCharacterNum = get(l:options, 'maxCharacterNum', &textwidth > 0 ? &textwidth : 80) + + if type(a:expr) == type([]) + let l:source = map(a:expr, 'l:repl . join(split(v:val, "[^[:alnum:]]\\+"), l:repl) . l:repl') + else + let l:source = split(a:expr, "[^[:alnum:]]\\+") + endif + + while ingo#compat#strchars(s:Render(l:source, l:repl)) > l:maxCharacterNum + if l:removeFrom ==# 'm' && len(l:source) == 2 + " Special case: take the larger one that still fits. + let l:len0 = ingo#compat#strchars(s:Render([l:source[0]], l:repl)) + let l:len1 = ingo#compat#strchars(s:Render([l:source[1]], l:repl)) + + if l:len0 >= l:len1 && l:len0 <= l:maxCharacterNum + let l:source = [l:source[0]] + elseif l:len1 >= l:len0 && l:len1 <= l:maxCharacterNum + let l:source = [l:source[1]] + else + let l:source = [l:source[(l:len0 > l:len1 ? 1 : 0)]] + endif + elseif len(l:source) > 1 + if l:removeFrom ==# 'm' + let l:dropIdx = len(l:source) / 2 + elseif l:removeFrom ==# 'l' + let l:dropIdx = 0 + elseif l:removeFrom ==# 'r' + let l:dropIdx = -1 + else + throw 'ASSERT: Invalid a:options.removeFrom: ' . string(l:removeFrom) + endif + call remove(l:source, l:dropIdx) + elseif stridx(l:source[0], l:repl) != -1 + " The part can be broken into sub-parts. + let l:source = split(l:source[0], '\V\C' . escape(l:repl, '\')) + else + return matchstr(s:Render(l:source, l:repl), '^.\{' . l:maxCharacterNum . '}') + endif + endwhile + return s:Render(l:source, l:repl) +endfunction +function! s:Render( source, repl ) + let l:render = join(a:source, a:repl) + let l:r = escape(a:repl, '\') + return substitute(l:render, printf('\V\C\^%s\+\|%s\+\$\|%s\{2}\zs%s\+', l:r, l:r, l:r, l:r), '', 'g') +endfunction + " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :