summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--doc/ranger.pod7
-rw-r--r--examples/rc_emacs.conf1
-rw-r--r--ranger/api/commands.py2
-rw-r--r--ranger/config/commands.py14
-rw-r--r--ranger/config/rifle.conf8
-rw-r--r--ranger/core/actions.py107
-rw-r--r--ranger/core/fm.py7
-rw-r--r--ranger/core/loader.py10
-rwxr-xr-xranger/data/scope.sh8
-rwxr-xr-xranger/ext/rifle.py9
-rw-r--r--ranger/gui/colorscheme.py2
-rw-r--r--ranger/gui/curses_shortcuts.py8
-rw-r--r--ranger/gui/widgets/__init__.py2
-rw-r--r--ranger/gui/widgets/browsercolumn.py7
-rw-r--r--ranger/gui/widgets/titlebar.py4
16 files changed, 153 insertions, 47 deletions
diff --git a/README.md b/README.md
index f0d3ca25..57b2631d 100644
--- a/README.md
+++ b/README.md
@@ -52,8 +52,8 @@ Features
 
 Dependencies
 ------------
-* Python (tested with version 2.6, 2.7, 3.1, 3.2) with support for ncurses
-  and (optionally) wide-unicode.
+* Python (tested with version 2.6, 2.7, 3.1, 3.2) with the "curses" module
+  and (optionally) wide-unicode support.
 * A pager ("less" by default)
 
 Optional:
diff --git a/doc/ranger.pod b/doc/ranger.pod
index b68595a9..6c66f817 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -201,12 +201,13 @@ Macros can be used in commands to abbreviate things.
 
  %f   the highlighted file
  %d   the path of the current directory
- %s   the selected files in the current directory.
+ %s   the selected files in the current directory
  %t   all tagged files in the current directory
  %c   the full paths of the currently copied/cut files
+ %p   the full paths of selected files
 
-The macros %f, %d and %s also have upper case variants, %F, %D and %S,
-which refer to the next tab.  To refer to specific tabs, add a number in
+The macros %f, %d, %p, and %s also have upper case variants, %F, %D, %P, and
+%S, which refer to the next tab.  To refer to specific tabs, add a number in
 between.  (%7s = selection of the seventh tab.)
 
 %c is the only macro which ranges out of the current directory. So you may
diff --git a/examples/rc_emacs.conf b/examples/rc_emacs.conf
index e8b76ff2..f5e66b89 100644
--- a/examples/rc_emacs.conf
+++ b/examples/rc_emacs.conf
@@ -419,7 +419,6 @@ eval for arg in "rwxXst": cmd("map <C-x>-{0}  shell -f chmod u-{0} %s".format(ar
 
 # Search for letters as you type them
 #eval for arg in "abcdefghijklmnopqrstuvwxyz": cmd("map {0} console search_inc {0}".format(arg))
-map <allow_quantifiers> false
 
 # ===================================================================
 # == Define keys for the console
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 2cf96a9f..ca713d0c 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -47,7 +47,7 @@ class CommandContainer(object):
                 continue
             attribute = getattr(obj, attribute_name)
             if hasattr(attribute, '__call__'):
-                cmd = type(attribute_name, (FunctionCommand, ), dict())
+                cmd = type(attribute_name, (FunctionCommand, ), dict(__doc__=attribute.__doc__))
                 cmd._based_function = attribute
                 cmd._function_name = attribute.__name__
                 cmd._object_name = obj.__class__.__name__
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index 9f0481ce..4f7f109a 100644
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -226,7 +226,7 @@ class shell(Command):
         else:
             before_word, start_of_word = self.line.rsplit(' ', 1)
             return (before_word + ' ' + file.shell_escaped_basename \
-                    for file in self.fm.thisdir.files \
+                    for file in self.fm.thisdir.files or [] \
                     if file.shell_escaped_basename.startswith(start_of_word))
 
 class open_with(Command):
@@ -342,12 +342,12 @@ class set_(Command):
         if not name:
             return sorted(self.firstpart + setting for setting in settings)
         if not value and not name_done:
-            return (self.firstpart + setting for setting in settings \
+            return sorted(self.firstpart + setting for setting in settings \
                     if setting.startswith(name))
         if not value:
             # Cycle through colorschemes when name, but no value is specified
             if name == "colorscheme":
-                return (self.firstpart + colorscheme for colorscheme \
+                return sorted(self.firstpart + colorscheme for colorscheme \
                         in get_all_colorschemes())
             return self.firstpart + str(settings[name])
         if bool in settings.types_of(name):
@@ -357,7 +357,7 @@ class set_(Command):
                 return self.firstpart + 'False'
         # Tab complete colorscheme values if incomplete value is present
         if name == "colorscheme":
-            return (self.firstpart + colorscheme for colorscheme \
+            return sorted(self.firstpart + colorscheme for colorscheme \
                     in get_all_colorschemes() if colorscheme.startswith(value))
 
 
@@ -548,7 +548,7 @@ class mark_tag(Command):
     def execute(self):
         cwd = self.fm.thisdir
         tags = self.rest(1).replace(" ","")
-        if not self.fm.tags:
+        if not self.fm.tags or not cwd.files:
             return
         for fileobj in cwd.files:
             try:
@@ -1131,7 +1131,7 @@ class scout(Command):
         self.fm.thistab.last_search = regex
         self.fm.set_search_method(order="search")
 
-        if self.MARK in flags or self.UNMARK in flags:
+        if (self.MARK in flags or self.UNMARK in flags) and thisdir.files:
             value = flags.find(self.MARK) > flags.find(self.UNMARK)
             if self.FILTER in flags:
                 for f in thisdir.files:
@@ -1234,7 +1234,7 @@ class scout(Command):
         cwd     = self.fm.thisdir
         pattern = self.pattern
 
-        if not pattern:
+        if not pattern or not cwd.files:
             return 0
         if pattern == '.':
             return 0
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index 95e4242a..31db35a4 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -57,6 +57,7 @@
 ext x?html?, has surf,           X, flag f = surf -- file://"$1"
 ext x?html?, has vimprobable,    X, flag f = vimprobable -- "$@"
 ext x?html?, has vimprobable2,   X, flag f = vimprobable2 -- "$@"
+ext x?html?, has qutebrowser,    X, flag f = qutebrowser -- "$@"
 ext x?html?, has dwb,            X, flag f = dwb -- "$@"
 ext x?html?, has jumanji,        X, flag f = jumanji -- "$@"
 ext x?html?, has luakit,         X, flag f = luakit -- "$@"
@@ -107,9 +108,9 @@ ext php = php -- "$1"
 #--------------------------------------------
 # Audio without X
 #-------------------------------------------
-mime ^audio|ogg$, terminal, has mplayer  = mplayer -- "$@"
-mime ^audio|ogg$, terminal, has mplayer2 = mplayer2 -- "$@"
 mime ^audio|ogg$, terminal, has mpv      = mpv -- "$@"
+mime ^audio|ogg$, terminal, has mplayer2 = mplayer2 -- "$@"
+mime ^audio|ogg$, terminal, has mplayer  = mplayer -- "$@"
 ext midi?,        terminal, has wildmidi = wildmidi -- "$@"
 
 #--------------------------------------------
@@ -201,3 +202,6 @@ label wallpaper, number 14, mime ^image, has feh, X = feh --bg-fill "$1"
               !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php  = ask
 label editor, !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php  = $EDITOR -- "$@"
 label pager,  !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php  = "$PAGER" -- "$@"
+
+# The very last action, so that it's never triggered accidently, is to execute a program:
+mime application/x-executable = "$1"
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 8167dbcf..cbf75dbf 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -41,11 +41,17 @@ class Actions(FileManagerAware, SettingsAware):
     # --------------------------
 
     def exit(self):
-        """Exit the program"""
+        """:exit
+
+        Exit the program.
+        """
         raise SystemExit()
 
     def reset(self):
-        """Reset the filemanager, clearing the directory buffer"""
+        """:reset
+
+        Reset the filemanager, clearing the directory buffer.
+        """
         old_path = self.thisdir.path
         self.previews = {}
         self.garbage_collect(-1)
@@ -55,6 +61,10 @@ class Actions(FileManagerAware, SettingsAware):
             self.metadata.reset()
 
     def change_mode(self, mode):
+        """:change_mode <mode>
+
+        Change mode to "visual" (selection) or "normal" mode.
+        """
         if mode == self.mode:
             return
         if mode == 'visual':
@@ -103,6 +113,10 @@ class Actions(FileManagerAware, SettingsAware):
         raise ValueError("Invalid value `%s' for option `%s'!" % (name, value))
 
     def toggle_visual_mode(self, reverse=False, narg=None):
+        """:toggle_visual_mode
+
+        Toggle the visual mode (see :change_mode).
+        """
         if self.mode == 'normal':
             self._visual_reverse = reverse
             if narg != None:
@@ -112,14 +126,23 @@ class Actions(FileManagerAware, SettingsAware):
             self.change_mode('normal')
 
     def reload_cwd(self):
+        """:reload_cwd
+
+        Reload the current working directory.
+        """
         try:
             cwd = self.thisdir
         except:
             pass
-        cwd.unload()
-        cwd.load_content()
+        else:
+            cwd.unload()
+            cwd.load_content()
 
     def notify(self, text, duration=4, bad=False):
+        """:notify <text>
+
+        Display the text in the statusbar.
+        """
         if isinstance(text, Exception):
             if ranger.arg.debug:
                 raise
@@ -135,6 +158,10 @@ class Actions(FileManagerAware, SettingsAware):
             print(text)
 
     def abort(self):
+        """:abort
+
+        Empty the first queued action.
+        """
         try:
             item = self.loader.queue[0]
         except:
@@ -150,16 +177,25 @@ class Actions(FileManagerAware, SettingsAware):
         self.ui.redraw_main_column()
 
     def redraw_window(self):
-        """Redraw the window"""
+        """:redraw
+
+        Redraw the window.
+        """
         self.ui.redraw_window()
 
     def open_console(self, string='', prompt=None, position=None):
-        """Open the console"""
+        """:open_console [string]
+
+        Open the console.
+        """
         self.change_mode('normal')
         self.ui.open_console(string, prompt=prompt, position=position)
 
     def execute_console(self, string='', wildcards=[], quantifier=None):
-        """Execute a command for the console"""
+        """:execute_console [string]
+
+        Execute a command for the console
+        """
         command_name = string.lstrip().split()[0]
         cmd_class = self.commands.get_command(command_name, abbrev=False)
         if cmd_class is None:
@@ -217,8 +253,11 @@ class Actions(FileManagerAware, SettingsAware):
             macros['f'] = MACRO_FAIL
 
         if self.fm.thistab.get_selection:
+            macros['p'] = [os.path.join(self.fm.thisdir.path, fl.relative_path)
+                    for fl in self.fm.thistab.get_selection()]
             macros['s'] = [fl.relative_path for fl in self.fm.thistab.get_selection()]
         else:
+            macros['p'] = MACRO_FAIL
             macros['s'] = MACRO_FAIL
 
         if self.fm.copy_buffer:
@@ -237,7 +276,7 @@ class Actions(FileManagerAware, SettingsAware):
         else:
             macros['d'] = '.'
 
-        # define d/f/s macros for each tab
+        # define d/f/p/s macros for each tab
         for i in range(1,10):
             try:
                 tab = self.fm.tabs[i]
@@ -249,15 +288,18 @@ class Actions(FileManagerAware, SettingsAware):
             i = str(i)
             macros[i + 'd'] = tabdir.path
             if tabdir.get_selection():
+                macros[i + 'p'] = [os.path.join(tabdir.path, fl.relative_path)
+                        for fl in tabdir.get_selection()]
                 macros[i + 's'] = [fl.path for fl in tabdir.get_selection()]
             else:
+                macros[i + 'p'] = MACRO_FAIL
                 macros[i + 's'] = MACRO_FAIL
             if tabdir.pointed_obj:
                 macros[i + 'f'] = tabdir.pointed_obj.path
             else:
                 macros[i + 'f'] = MACRO_FAIL
 
-        # define D/F/S for the next tab
+        # define D/F/P/S for the next tab
         found_current_tab = False
         next_tab = None
         first_tab = None
@@ -280,8 +322,11 @@ class Actions(FileManagerAware, SettingsAware):
             else:
                 macros['F'] = MACRO_FAIL
             if next_tab_dir.get_selection():
+                macros['P'] = [os.path.join(next_tab.path, fl.path)
+                        for fl in next_tab.get_selection()]
                 macros['S'] = [fl.path for fl in next_tab.get_selection()]
             else:
+                macros['P'] = MACRO_FAIL
                 macros['S'] = MACRO_FAIL
         else:
             macros['D'] = MACRO_FAIL
@@ -291,6 +336,10 @@ class Actions(FileManagerAware, SettingsAware):
         return macros
 
     def source(self, filename):
+        """:source <filename>
+
+        Load a config file.
+        """
         filename = os.path.expanduser(filename)
         for line in open(filename, 'r'):
             line = line.lstrip().rstrip("\r\n")
@@ -538,12 +587,18 @@ class Actions(FileManagerAware, SettingsAware):
         self.execute_file(file, label='editor')
 
     def toggle_option(self, string):
-        """Toggle a boolean option named <string>"""
+        """:toggle_option <string>
+
+        Toggle a boolean option named <string>.
+        """
         if isinstance(self.settings[string], bool):
             self.settings[string] ^= True
 
     def set_option(self, optname, value):
-        """Set the value of an option named <optname>"""
+        """:set_option <optname>
+
+        Set the value of an option named <optname>.
+        """
         self.settings[optname] = value
 
     def sort(self, func=None, reverse=None):
@@ -680,6 +735,10 @@ class Actions(FileManagerAware, SettingsAware):
     # file is important to you in any context.
 
     def tag_toggle(self, paths=None, value=None, movedown=None, tag=None):
+        """:tag_toggle <character>
+
+        Toggle a tag <character>.
+        """
         if not self.tags:
             return
         if paths is None:
@@ -1113,8 +1172,10 @@ class Actions(FileManagerAware, SettingsAware):
         for cmd_name in sorted(self.commands.commands):
             cmd = self.commands.commands[cmd_name]
             if hasattr(cmd, '__doc__') and cmd.__doc__:
-                write(cleandoc(cmd.__doc__))
-                write("\n\n" + "-" * 60 + "\n")
+                doc = cleandoc(cmd.__doc__)
+                if doc[0] == ':':
+                    write(doc)
+                    write("\n\n" + "-" * 60 + "\n")
             else:
                 undocumented.append(cmd)
 
@@ -1144,12 +1205,20 @@ class Actions(FileManagerAware, SettingsAware):
     # --------------------------
 
     def uncut(self):
+        """:uncut
+
+        Empty the copy buffer.
+        """
         self.copy_buffer = set()
         self.do_cut = False
         self.ui.browser.main_column.request_redraw()
 
     def copy(self, mode='set', narg=None, dirarg=None):
-        """Copy the selected items.  Modes are: 'set', 'add', 'remove'."""
+        """:copy [mode=set]
+
+        Copy the selected items.
+        Modes are: 'set', 'add', 'remove'.
+        """
         assert mode in ('set', 'add', 'remove')
         cwd = self.thisdir
         if not narg and not dirarg:
@@ -1176,6 +1245,11 @@ class Actions(FileManagerAware, SettingsAware):
         self.ui.browser.main_column.request_redraw()
 
     def cut(self, mode='set', narg=None, dirarg=None):
+        """:cut [mode=set]
+
+        Cut the selected items.
+        Modes are: 'set, 'add, 'remove.
+        """
         self.copy(mode=mode, narg=narg, dirarg=dirarg)
         self.do_cut = True
         self.ui.browser.main_column.request_redraw()
@@ -1224,7 +1298,10 @@ class Actions(FileManagerAware, SettingsAware):
                     next_available_filename(target_path))
 
     def paste(self, overwrite=False, append=False):
-        """Paste the selected items into the current directory"""
+        """:paste
+
+        Paste the selected items into the current directory.
+        """
         loadable = CopyLoader(self.copy_buffer, self.do_cut, overwrite)
         self.loader.add(loadable, append=append)
         self.do_cut = False
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 0313639a..046eb788 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -136,7 +136,7 @@ class FM(Actions, SignalDispatcher):
 
             if self.settings.open_all_images and \
                     len(self.thisdir.marked_items) == 0 and \
-                    re.match(r'^(feh|sxiv) ', command):
+                    re.match(r'^(feh|sxiv|imv) ', command):
 
                 images = [f.relative_path for f in self.thisdir.files if f.image]
                 escaped_filenames = " ".join(shell_quote(f) \
@@ -156,6 +156,11 @@ class FM(Actions, SignalDispatcher):
                             "feh --start-at %s " % \
                             shell_quote(self.thisfile.relative_path), 1)
 
+                    if command[0:4] == 'imv ':
+                        number = images.index(self.thisfile.relative_path) + 1
+                        new_command = command.replace("imv ",
+                                "imv -n %d " % number, 1)
+
                     if new_command:
                         command = "set -- %s; %s" % (escaped_filenames,
                                 new_command)
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 411b16ec..2184d2b1 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -6,6 +6,7 @@ from time import time, sleep
 from subprocess import Popen, PIPE
 from ranger.core.shared import FileManagerAware
 from ranger.ext.signals import SignalDispatcher
+from ranger.ext.human_readable import human_readable
 import math
 import os.path
 import sys
@@ -77,13 +78,14 @@ class CopyLoader(Loadable, FileManagerAware):
             # TODO: Don't calculate size when renaming (needs detection)
             bytes_per_tick = shutil_g.BLOCK_SIZE
             size = max(1, self._calculate_size(bytes_per_tick))
+            size_str = " (" + human_readable(self._calculate_size(1)) + ")"
             done = 0
             if self.do_cut:
                 self.original_copy_buffer.clear()
                 if len(self.copy_buffer) == 1:
-                    self.description = "moving: " + self.one_file.path
+                    self.description = "moving: " + self.one_file.path + size_str
                 else:
-                    self.description = "moving files from: " + self.one_file.dirname
+                    self.description = "moving files from: " + self.one_file.dirname + size_str
                 for f in self.copy_buffer:
                     for tf in self.fm.tags.tags:
                         if tf == f.path or str(tf).startswith(f.path):
@@ -101,9 +103,9 @@ class CopyLoader(Loadable, FileManagerAware):
                     done += d
             else:
                 if len(self.copy_buffer) == 1:
-                    self.description = "copying: " + self.one_file.path
+                    self.description = "copying: " + self.one_file.path + size_str
                 else:
-                    self.description = "copying files from: " + self.one_file.dirname
+                    self.description = "copying files from: " + self.one_file.dirname + size_str
                 for f in self.copy_buffer:
                     if os.path.isdir(f.path) and not os.path.islink(f.path):
                         d = 0
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 19404019..afaf131f 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -30,7 +30,7 @@ maxln=200    # Stop after $maxln lines.  Can be used like ls | head -n $maxln
 
 # Find out something about the file:
 mimetype=$(file --mime-type -Lb "$path")
-extension=$(/bin/echo "${path##*.}" | tr "[:upper:]" "[:lower:]")
+extension=$(/bin/echo "${path##*.}" | awk '{print tolower($0)}')
 
 # Functions:
 # runs a command and saves its output into $output.  Useful if you need
@@ -44,7 +44,7 @@ dump() { /bin/echo "$output"; }
 trim() { head -n "$maxln"; }
 
 # wraps highlight to treat exit code 141 (killed by SIGPIPE) as success
-highlight() { command highlight "$@"; test $? = 0 -o $? = 141; }
+safepipe() { "$@"; test $? = 0 -o $? = 141; }
 
 # Image previews, if enabled in ranger.
 if [ "$preview_images" = "True" ]; then
@@ -91,7 +91,9 @@ esac
 case "$mimetype" in
     # Syntax highlight for text files:
     text/* | */xml)
-        try highlight --out-format=ansi "$path" && { dump | trim; exit 5; } || exit 2;;
+        try safepipe highlight --out-format=ansi "$path" && { dump | trim; exit 5; }
+        try safepipe pygmentize "$path" && { dump | trim; exit 5; }
+        exit 2;;
     # Ascii-previews of images:
     image/*)
         img2txt --gamma=0.6 --width="$width" "$path" && exit 4 || exit 1;;
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 504851b1..c43de24f 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -195,8 +195,11 @@ class Rifle(object):
         argument = rule[1] if len(rule) > 1 else ''
 
         if function == 'ext':
-            extension = os.path.basename(files[0]).rsplit('.', 1)[-1].lower()
-            return bool(re.search('^(' + argument + ')$', extension))
+            if os.path.isfile(files[0]):
+                partitions = os.path.basename(files[0]).rpartition('.')
+                if not partitions[0]:
+                    return False
+                return bool(re.search('^(' + argument + ')$', partitions[2].lower()))
         elif function == 'name':
             return bool(re.search(argument, os.path.basename(files[0])))
         elif function == 'match':
@@ -231,7 +234,7 @@ class Rifle(object):
             self._app_flags = argument
             return True
         elif function == 'X':
-            return 'DISPLAY' in os.environ
+            return sys.platform == 'darwin' or 'DISPLAY' in os.environ
         elif function == 'env':
             return bool(os.environ.get(argument))
         elif function == 'else':
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index d6afcacc..d2b3b2d2 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -86,7 +86,7 @@ def _colorscheme_name_to_class(signal):
     usecustom = not ranger.arg.clean
 
     def exists(colorscheme):
-        return os.path.exists(colorscheme + '.py')
+        return os.path.exists(colorscheme + '.py') or os.path.exists(colorscheme + '.pyc')
 
     def is_scheme(x):
         try:
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index 187891c6..e7573f17 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -25,20 +25,28 @@ class CursesShortcuts(SettingsAware):
     """
 
     def addstr(self, *args):
+        y, x = self.win.getyx()
+
         try:
             self.win.addstr(*args)
         except:
             if len(args) > 1:
+                self.win.move(y, x)
+
                 try:
                     self.win.addstr(*_fix_surrogates(args))
                 except:
                     pass
 
     def addnstr(self, *args):
+        y, x = self.win.getyx()
+
         try:
             self.win.addnstr(*args)
         except:
             if len(args) > 2:
+                self.win.move(y, x)
+
                 try:
                     self.win.addnstr(*_fix_surrogates(args))
                 except:
diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py
index f2e52c0a..bd0f2337 100644
--- a/ranger/gui/widgets/__init__.py
+++ b/ranger/gui/widgets/__init__.py
@@ -21,3 +21,5 @@ class Widget(Displayable):
                 'ahead':    ('>', ["vcsahead"]),
                 'diverged': ('Y', ["vcsdiverged"]),
                 'unknown':  ('?', ["vcsunknown"])}
+
+    ellipsis = { False: '~', True: '…' }
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index b38d0b5b..52ef62b8 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of ranger, the console file manager.
 # License: GNU GPL version 3, see the file "AUTHORS" for details.
 
@@ -7,6 +6,7 @@
 import curses
 import stat
 from time import time
+from os.path import splitext
 
 from . import Widget
 from .pager import Pager
@@ -23,7 +23,6 @@ class BrowserColumn(Pager):
     scroll_begin = 0
     target = None
     last_redraw_time = -1
-    ellipsis = { False: '~', True: '…' }
 
     old_dir = None
     old_thisfile = None
@@ -342,8 +341,12 @@ class BrowserColumn(Pager):
 
     def _draw_text_display(self, text, space):
         wtext = WideString(text)
+        wext = WideString(splitext(text)[1])
         wellip = WideString(self.ellipsis[self.settings.unicode_ellipsis])
         if len(wtext) > space:
+            wtext = wtext[:max(1, space - len(wext) - len(wellip))] + wellip + wext
+        # Truncate again if still too long.
+        if len(wtext) > space:
             wtext = wtext[:max(0, space - len(wellip))] + wellip
 
         return [[str(wtext), []]]
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index c3785986..dbd08981 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -98,7 +98,7 @@ class TitleBar(Widget):
         bar.add(self.fm.username, 'hostname', clr, fixed=True)
         bar.add('@', 'hostname', clr, fixed=True)
         bar.add(self.fm.hostname, 'hostname', clr, fixed=True)
-        bar.add(':', 'hostname', clr, fixed=True)
+        bar.add(' ', 'hostname', clr, fixed=True)
 
         pathway = self.fm.thistab.pathway
         if self.settings.tilde_in_titlebar and \
@@ -140,7 +140,7 @@ class TitleBar(Widget):
             if not dirname:
                 result += ":/"
             elif len(dirname) > 15:
-                result += ":" + dirname[:14] + "~"
+                result += ":" + dirname[:14] + self.ellipsis[self.settings.unicode_ellipsis]
             else:
                 result += ":" + dirname
         return result