Merge pull request #725 from ZyX-I/fix-fname-special-chars

Handle non-printable characters properly
This commit is contained in:
ZyX-I 2014-01-26 10:04:30 -08:00
commit 417884a3ed
20 changed files with 224 additions and 54 deletions

View File

@ -1,6 +1,7 @@
# vim:fileencoding=utf-8:noet
import sys
import codecs
try:
import vim
@ -113,4 +114,34 @@ class VimEnviron(object):
+ value.replace('"', '\\"').replace('\\', '\\\\').replace('\n', '\\n').replace('\0', '')
+ '"')
if sys.version_info < (3,):
def buffer_name(buf):
return buf.name
else:
vim_bufname = vim_get_func('bufname')
def buffer_name(buf): # NOQA
try:
name = buf.name
except UnicodeDecodeError:
return vim_bufname(buf.number)
else:
return name.encode('utf-8') if name else None
vim_strtrans = vim_get_func('strtrans')
def powerline_vim_strtrans_error(e):
if not isinstance(e, UnicodeDecodeError):
raise NotImplementedError
# Assuming &encoding is utf-8 strtrans should not return anything but ASCII
# under current circumstances
text = vim_strtrans(e.object[e.start:e.end]).decode()
return (text, e.end)
codecs.register_error('powerline_vim_strtrans_error', powerline_vim_strtrans_error)
environ = VimEnviron()

View File

@ -4,12 +4,15 @@ from powerline.theme import Theme
from unicodedata import east_asian_width, combining
import os
try:
NBSP = unicode(' ', 'utf-8')
except NameError:
NBSP = ' '
try:
from __builtin__ import unichr as chr
except ImportError:
pass
def construct_returned_value(rendered_highlighted, segments, output_raw):
if output_raw:
@ -65,6 +68,22 @@ class Renderer(object):
python-2) regular string or ``None``.
'''
character_translations = {ord(' '): NBSP}
'''Character translations for use in escape() function.
See documentation of ``unicode.translate`` for details.
'''
np_character_translations = dict(((i, '^' + chr(i + 0x40)) for i in range(0x20)))
'''Non-printable character translations
These are used to transform characters in range 0x000x1F into ``^@``,
``^A`` and so on. Unilke with ``.escape()`` method (and
``character_translations``) result is passed to ``.strwidth()`` method.
Note: transforms tab into ``^I``.
'''
def __init__(self,
theme_config,
local_themes,
@ -262,8 +281,8 @@ class Renderer(object):
contents_raw = (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding
# Replace spaces with no-break spaces
contents_raw = contents_raw.replace(' ', NBSP)
divider_raw = divider_raw.replace(' ', NBSP)
contents_raw = contents_raw.translate(self.np_character_translations)
# Apply highlighting to padded dividers and contents
if render_highlighted:
@ -295,11 +314,11 @@ class Renderer(object):
segment['_len'] = self.strwidth(segment['_rendered_raw'])
yield segment
@staticmethod
def escape(string):
@classmethod
def escape(cls, string):
'''Method that escapes segment contents.
'''
return string
return string.translate(cls.character_translations)
def hlstyle(fg=None, bg=None, attr=None):
'''Output highlight style string.

View File

@ -1,5 +1,7 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import, unicode_literals
from powerline.renderers.shell import ShellRenderer
@ -8,9 +10,10 @@ class BashPromptRenderer(ShellRenderer):
escape_hl_start = '\['
escape_hl_end = '\]'
@staticmethod
def escape(string):
return string.replace('\\', '\\\\').replace('$', '\\$').replace('`', '\\`')
character_translations = ShellRenderer.character_translations.copy()
character_translations[ord('$')] = '\\$'
character_translations[ord('`')] = '\\`'
character_translations[ord('\\')] = '\\\\'
renderer = BashPromptRenderer

View File

@ -1,5 +1,7 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import, unicode_literals
from powerline.renderer import Renderer
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
@ -19,6 +21,8 @@ class ShellRenderer(Renderer):
tmux_escape = False
screen_escape = False
character_translations = Renderer.character_translations.copy()
def hlstyle(self, fg=None, bg=None, attr=None):
'''Highlight a segment.
@ -62,9 +66,5 @@ class ShellRenderer(Renderer):
r = '\033P' + r.replace('\033', '\033\033') + '\033\\'
return self.escape_hl_start + r + self.escape_hl_end
@staticmethod
def escape(string):
return string.replace('\\', '\\\\')
renderer = ShellRenderer

View File

@ -1,11 +1,17 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import, unicode_literals
from powerline.renderer import Renderer
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
class TmuxRenderer(Renderer):
'''Powerline tmux segment renderer.'''
character_translations = Renderer.character_translations.copy()
character_translations[ord('#')] = '##[]'
def hlstyle(self, fg=None, bg=None, attr=None):
'''Highlight a segment.'''
# We don't need to explicitly reset attributes, so skip those calls

View File

@ -1,6 +1,6 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals
from powerline.bindings.vim import vim_get_func, environ
from powerline.renderer import Renderer
@ -11,6 +11,12 @@ import vim
import sys
try:
from __builtin__ import unichr as chr
except ImportError:
pass
vim_mode = vim_get_func('mode', rettype=str)
mode_translations = {
chr(ord('V') - 0x40): '^V',
@ -21,6 +27,9 @@ mode_translations = {
class VimRenderer(Renderer):
'''Powerline vim segment renderer.'''
character_translations = Renderer.character_translations.copy()
character_translations[ord('%')] = '%%'
def __init__(self, *args, **kwargs):
if not hasattr(vim, 'strwidth'):
# Hope nobody want to change this at runtime
@ -97,10 +106,6 @@ class VimRenderer(Renderer):
def reset_highlight(self):
self.hl_groups.clear()
@staticmethod
def escape(string):
return string.replace('%', '%%')
def hlstyle(self, fg=None, bg=None, attr=None):
'''Highlight a segment.

View File

@ -1,5 +1,7 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import, unicode_literals
from powerline.renderers.shell import ShellRenderer
@ -8,9 +10,8 @@ class ZshPromptRenderer(ShellRenderer):
escape_hl_start = '%{'
escape_hl_end = '%}'
@staticmethod
def escape(string):
return string.replace('%', '%%').replace('\\', '\\\\')
character_translations = ShellRenderer.character_translations.copy()
character_translations[ord('%')] = '%%'
renderer = ZshPromptRenderer

View File

@ -1,6 +1,6 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import
from __future__ import unicode_literals, absolute_import
import os
import sys

View File

@ -1,6 +1,6 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import, division
from __future__ import unicode_literals, absolute_import, division
import os
try:
@ -8,7 +8,8 @@ try:
except ImportError:
vim = {} # NOQA
from powerline.bindings.vim import vim_get_func, getbufvar, vim_getbufoption
from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption,
buffer_name)
from powerline.theme import requires_segment_info
from powerline.lib import add_divider_highlight_group
from powerline.lib.vcs import guess, tree_status
@ -19,8 +20,8 @@ from collections import defaultdict
vim_funcs = {
'virtcol': vim_get_func('virtcol', rettype=int),
'getpos': vim_get_func('getpos'),
'fnamemodify': vim_get_func('fnamemodify', rettype=str),
'expand': vim_get_func('expand', rettype=str),
'fnamemodify': vim_get_func('fnamemodify'),
'expand': vim_get_func('expand'),
'bufnr': vim_get_func('bufnr', rettype=int),
'line2byte': vim_get_func('line2byte', rettype=int),
'line': vim_get_func('line', rettype=int),
@ -157,14 +158,18 @@ def file_directory(pl, segment_info, shorten_user=True, shorten_cwd=True, shorte
:param bool shorten_home:
shorten all directories in :file:`/home/` to :file:`~user/` instead of :file:`/home/user/`.
'''
name = segment_info['buffer'].name
name = buffer_name(segment_info['buffer'])
if not name:
return None
import sys
file_directory = vim_funcs['fnamemodify'](name, (':~' if shorten_user else '')
+ (':.' if shorten_cwd else '') + ':h')
if not file_directory:
return None
if shorten_home and file_directory.startswith('/home/'):
file_directory = '~' + file_directory[6:]
return file_directory + os.sep if file_directory else None
file_directory = b'~' + file_directory[6:]
file_directory = file_directory.decode('utf-8', 'powerline_vim_strtrans_error')
return file_directory + os.sep
@requires_segment_info
@ -178,7 +183,7 @@ def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]')
Highlight groups used: ``file_name_no_file`` or ``file_name``, ``file_name``.
'''
name = segment_info['buffer'].name
name = buffer_name(segment_info['buffer'])
if not name:
if display_no_file:
return [{
@ -187,8 +192,7 @@ def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]')
}]
else:
return None
file_name = vim_funcs['fnamemodify'](name, ':~:.:t')
return file_name
return os.path.basename(name).decode('utf-8', 'powerline_vim_strtrans_error')
@window_cached

View File

@ -1,5 +1,7 @@
# vim:fileencoding=utf-8:noet
from __future__ import unicode_literals
from powerline.segments import shell, common
import tests.vim as vim_module
import sys
@ -467,6 +469,10 @@ class TestVim(TestCase):
segment_info = vim_module._get_segment_info()
self.assertEqual(vim.file_directory(pl=pl, segment_info=segment_info), None)
with replace_env('HOME', '/home/foo', os.environ):
with vim_module._with('buffer', '/tmp//abc') as segment_info:
self.assertEqual(vim.file_directory(pl=pl, segment_info=segment_info), '/tmp//')
with vim_module._with('buffer', b'/tmp/\xFF\xFF/abc') as segment_info:
self.assertEqual(vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/<ff><ff>/')
with vim_module._with('buffer', '/tmp/abc') as segment_info:
self.assertEqual(vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/')
os.environ['HOME'] = '/tmp'
@ -484,6 +490,8 @@ class TestVim(TestCase):
self.assertEqual(vim.file_name(pl=pl, segment_info=segment_info), 'abc')
with vim_module._with('buffer', '/tmp/') as segment_info:
self.assertEqual(vim.file_name(pl=pl, segment_info=segment_info), '')
with vim_module._with('buffer', b'/tmp/\xFF\xFF') as segment_info:
self.assertEqual(vim.file_name(pl=pl, segment_info=segment_info), '<ff><ff>')
def test_file_size(self):
pl = Pl()

View File

@ -7,4 +7,12 @@
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  false
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  1  kill `cat pid` ; sleep 1s
[1]+ Terminated bash -c "echo \$\$>pid ; while true ; do sleep 0.1s ; done"
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  false
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  cd "$DIR1"
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^[[32m  cd ../"$DIR2"
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^H  cd ../'\[\]'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  \[\]  cd ../'%%'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  %%  cd ../'#[bold]'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  #[bold]  cd ../'(echo)'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  (echo)  cd ../'$(echo)'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  $(echo)  cd ../'`echo`'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  `echo`  false

View File

@ -6,3 +6,11 @@
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  1  
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^[[32m  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^H  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  \[\]  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  %%  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  #[bold]  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  (echo)  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  $(echo)  
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  `echo`  

View File

@ -10,6 +10,14 @@ VIRTUAL_ENV=
bash -c "echo \$\$>pid ; while true ; do sleep 0.1s ; done" &
false
kill `cat pid` ; sleep 1s
cd "$DIR1"
cd ../"$DIR2"
cd ../'\[\]'
cd ../'%%'
cd ../'#[bold]'
cd ../'(echo)'
cd ../'$(echo)'
cd ../'`echo`'
false
true is the last line
exit

View File

@ -11,6 +11,14 @@ set VIRTUAL_ENV
bash -c "echo \$\$>pid ; while true ; do sleep 0.1s ; done" &
false
kill (cat pid) ; sleep 1s
cd "$DIR1"
cd ../"$DIR2"
cd ../'\[\]'
cd ../'%%'
cd ../'#[bold]'
cd ../'(echo)'
cd ../'$(echo)'
cd ../'`echo`'
false
true is the last line
exit

View File

@ -11,6 +11,14 @@ VIRTUAL_ENV=
bash -c "echo \$\$>pid ; while true ; do sleep 0.1s ; done" &
false
kill `cat pid` ; sleep 1s
cd "$DIR1"
cd ../"$DIR2"
cd ../'\[\]'
cd ../'%%'
cd ../'#[bold]'
cd ../'(echo)'
cd ../'$(echo)'
cd ../'`echo`'
false
true is the last line
exit

View File

@ -8,11 +8,11 @@ import sys
import codecs
fname = sys.argv[1]
new_fname = fname + '.new'
pid_fname = 'tests/shell/3rd/pid'
shell = sys.argv[1]
fname = os.path.join('tests', 'shell', shell + '.full.log')
new_fname = os.path.join('tests', 'shell', shell + '.log')
pid_fname = os.path.join('tests', 'shell', '3rd', 'pid')
shell = sys.argv[2]
with open(pid_fname, 'r') as P:
pid = P.read().strip()
@ -42,5 +42,3 @@ with codecs.open(fname, 'r', encoding='utf-8') as R:
except ValueError:
line = ''
W.write(line)
os.rename(new_fname, fname)

View File

@ -1,3 +1,3 @@
width 1024
height 1
logfile "tests/shell/screen.log"
logfile "tests/shell/${SH}.full.log"

View File

@ -3,11 +3,12 @@ FAILED=0
ONLY_SHELL="$1"
check_screen_log() {
if test -e tests/test_shells/${1}.ok ; then
diff -u tests/test_shells/${1}.ok tests/shell/screen.log
SH="$1"
if test -e tests/test_shells/${SH}.ok ; then
diff -u tests/test_shells/${SH}.ok tests/shell/${SH}.log
return $?
else
cat tests/shell/screen.log
cat tests/shell/${SH}.log
return 1
fi
}
@ -20,22 +21,21 @@ run_test() {
which "${SH}" || return 0
export SH
screen -L -c tests/test_shells/screenrc -d -m -S "$SESNAME" \
env LANG=en_US.UTF-8 BINDFILE="$BINDFILE" "$@"
screen -S "$SESNAME" -X readreg a tests/test_shells/input.$SH
# Wait for screen to initialize
sleep 1s
screen -S "$SESNAME" -p 0 -X width 300 1
screen -S "$SESNAME" -p 0 -X logfile tests/shell/screen.log
screen -S "$SESNAME" -p 0 -X paste a
# Wait for screen to exit (sending command to non-existing screen session
# fails; when launched instance exits corresponding session is deleted)
while screen -S "$SESNAME" -X blankerprg "" > /dev/null ; do
sleep 0.1s
done
cp tests/shell/screen.log tests/shell/${SH}.full.log
./tests/test_shells/postproc.py tests/shell/screen.log ${SH}
cp tests/shell/screen.log tests/shell/${SH}.log
./tests/test_shells/postproc.py ${SH}
if ! check_screen_log ${SH} ; then
echo '____________________________________________________________'
# Repeat the diff to make it better viewable in travis output
@ -52,10 +52,8 @@ run_test() {
cat -v tests/shell/${SH}.full.log
echo '____________________________________________________________'
${SH} --version
rm tests/shell/screen.log
return 1
fi
rm tests/shell/screen.log
return 0
}
@ -63,6 +61,16 @@ test -d tests/shell && rm -r tests/shell
mkdir tests/shell
git init tests/shell/3rd
git --git-dir=tests/shell/3rd/.git checkout -b BRANCH
export DIR1=""
export DIR2=""
mkdir tests/shell/3rd/"$DIR1"
mkdir tests/shell/3rd/"$DIR2"
mkdir tests/shell/3rd/'\[\]'
mkdir tests/shell/3rd/'%%'
mkdir tests/shell/3rd/'#[bold]'
mkdir tests/shell/3rd/'(echo)'
mkdir tests/shell/3rd/'$(echo)'
mkdir tests/shell/3rd/'`echo`'
if ! run_test bash --norc --noprofile -i ; then
FAILED=1

View File

@ -8,4 +8,12 @@
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  false
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  1  kill `cat pid` ; sleep 1s
[1] + terminated bash -c "echo \$\$>pid ; while true ; do sleep 0.1s ; done"
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  false
  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  cd "$DIR1"
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^[[32m  cd ../"$DIR2"
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^H  cd ../'\[\]'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  \[\]  cd ../'%%'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  %%  cd ../'#[bold]'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  #[bold]  cd ../'(echo)'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  (echo)  cd ../'$(echo)'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  $(echo)  cd ../'`echo`'
  HOSTNAME  USER   BRANCH  ⋯  shell  3rd  `echo`  false

View File

@ -60,7 +60,7 @@ class _Buffers(object):
@_vim
def __nonzero__(self):
return not not self.d
return bool(self.d)
@_vim
def keys(self):
@ -261,8 +261,8 @@ def _emul_getpos(expr):
def _emul_fnamemodify(path, modstring):
import os
_modifiers = {
'~': lambda path: path.replace(os.environ['HOME'], '~') if path.startswith(os.environ['HOME']) else path,
'.': lambda path: (lambda tpath: path if tpath[:3] == '..' + os.sep else tpath)(os.path.relpath(path)),
'~': lambda path: path.replace(os.environ['HOME'].encode('utf-8'), b'~') if path.startswith(os.environ['HOME'].encode('utf-8')) else path,
'.': lambda path: (lambda tpath: path if tpath[:3] == b'..' + os.sep.encode() else tpath)(os.path.relpath(path)),
't': lambda path: os.path.basename(path),
'h': lambda path: os.path.dirname(path),
}
@ -313,6 +313,22 @@ def _emul_line(expr):
raise NotImplementedError
@_vim
@_str_func
def _emul_strtrans(s):
# FIXME Do more replaces
return s.replace(b'\xFF', b'<ff>')
@_vim
@_str_func
def _emul_bufname(bufnr):
try:
return buffers[bufnr]._name or b''
except KeyError:
return b''
_window_ids = [None]
_window_id = 0
@ -348,11 +364,11 @@ _undo_written = {}
class _Buffer(object):
def __init__(self, name=None):
global _last_bufnr
import os
_last_bufnr += 1
bufnr = _last_bufnr
self.number = bufnr
self.name = os.path.abspath(name) if name else None
# FIXME Use unicode() for python-3
self.name = name
self.vars = {}
self.options = {
'modified': 0,
@ -369,6 +385,25 @@ class _Buffer(object):
_undo_written[bufnr] = len(_undostate[bufnr])
buffers[bufnr] = self
@property
def name(self):
import sys
if sys.version_info < (3,):
return self._name
else:
return str(self._name, 'utf-8') if self._name else None
@name.setter
def name(self, name):
if name is None:
self._name = None
else:
import os
if type(name) is not bytes:
name = name.encode('utf-8')
import sys
self._name = os.path.abspath(name)
def __getitem__(self, line):
return _buf_lines[self.number][line]
@ -676,3 +711,7 @@ def _with(key, *args, **kwargs):
return _WithDict(vars, **kwargs)
elif key == 'split':
return _WithSplit()
class error(Exception):
pass