-- text editor, particularly text drawing, horizontal wrap, vertical scrolling
Text = {}
local utf8 = require 'utf8'
require 'search'
require 'select'
require 'undo'
require 'text_tests'
-- return values:
-- y coordinate drawn until in px
-- position of start of final screen line drawn
function Text.draw(line, line_width, line_index)
--? print('text.draw')
love.graphics.setColor(0,0,0)
-- wrap long lines
local x = 25
local y = line.y
local pos = 1
local screen_line_starting_pos = 1
if line.fragments == nil then
Text.compute_fragments(line, line_width)
end
if line.screen_line_starting_pos == nil then
Text.populate_screen_line_starting_pos(line_index)
end
--? print('--')
for _, f in ipairs(line.fragments) do
local frag, frag_text = f.data, f.text
-- render fragment
local frag_width = App.width(frag_text)
local frag_len = utf8.len(frag)
--? local s=tostring
--? print('('..s(x)..','..s(y)..') '..frag..'('..s(frag_width)..' vs '..s(line_width)..') '..s(line_index)..' vs '..s(Screen_top1.line)..'; '..s(pos)..' vs '..s(Screen_top1.pos)..'; bottom: '..s(Screen_bottom1.line)..'/'..s(Screen_bottom1.pos))
if x + frag_width > line_width then
assert(x > 25) -- no overfull lines
-- update y only after drawing the first screen line of screen top
if Text.lt1(Screen_top1, {line=line_index, pos=pos}) then
y = y + Line_height
if y + Line_height > App.screen.height then
--? print('b', y, App.screen.height, '=>', screen_line_starting_pos)
return y, screen_line_starting_pos
end
screen_line_starting_pos = pos
--? print('text: new screen line', y, App.screen.height, screen_line_starting_pos)
end
x = 25
end
--? print('checking to draw', pos, Screen_top1.pos)
-- don't draw text above screen top
if Text.le1(Screen_top1, {line=line_index, pos=pos}) then
if Selection1.line then
local lo, hi = Text.clip_selection(line_index, pos, pos+frag_len)
Text.draw_highlight(line, x,y, pos, lo,hi)
end
--? print('drawing '..frag)
App.screen.draw(frag_text, x,y)
end
-- render cursor if necessary
if line_index == Cursor1.line then
if pos <= Cursor1.pos and pos + frag_len > Cursor1.pos then
if Search_term then
if Lines[Cursor1.line].data:sub(Cursor1.pos, Cursor1.pos+utf8.len(Search_term)-1) == Search_term then
local lo_px = Text.draw_highlight(line, x,y, pos, Cursor1.pos, Cursor1.pos+utf8.len(Search_term))
love.graphics.setColor(0,0,0)
love.graphics.print(Search_term, x+lo_px,y)
end
else
Text.draw_cursor(x+Text.x(frag, Cursor1.pos-pos+1), y)
end
end
end
x = x + frag_width
pos = pos + frag_len
end
if Search_term == nil then
if line_index == Cursor1.line and Cursor1.pos == pos then
Text.draw_cursor(x, y)
end
end
return y, screen_line_starting_pos
end
-- manual tests:
-- draw with small line_width of 100
function Text.draw_cursor(x, y)
-- blink every 0.5s
if math.floor(Cursor_time*2)%2 == 0 then
love.graphics.setColor(1,0,0)
love.graphics.rectangle('fill', x,y, 3,Line_height)
love.graphics.setColor(0,0,0)
end
Cursor_x = x
Cursor_y = y+Line_height
end
function Text.compute_fragments(line, line_width)
--? print('compute_fragments', line_width)
line.fragments = {}
local x = 25
-- try to wrap at word boundaries
for frag in line.data:gmatch('%S*%s*') do
local frag_text = App.newText(love.graphics.getFont(), frag)
local frag_width = App.width(frag_text)
--? print('x: '..tostring(x)..'; '..tostring(line_width-x)..'px to go')
--? print('frag: ^'..frag..'$ is '..tostring(frag_width)..'px wide')
if x + frag_width > line_width then
while x + frag_width > line_width do
--? print(x, frag, frag_width, line_width)
if x < 0.8*line_width then
--? print(frag, x, frag_width, line_width)
-- long word; chop it at some letter
-- We're not going to reimplement TeX here.
local b = Text.nearest_pos_less_than(frag, line_width - x)
assert(b > 0) -- avoid infinite loop when window is too narrow
--? print('space for '..tostring(b)..' graphemes')
local frag1 = string.sub(frag, 1, b)
local frag1_text = App.newText(love.graphics.getFont(), frag1)
local frag1_width = App.width(frag1_text)
--? print(frag, x, frag1_width, line_width)
assert(x + frag1_width <= line_width)
--? print('inserting '..frag1..' of width '..tostring(frag1_width)..'px')
table.insert(line.fragments, {data=frag1, text=frag1_text})
frag = string.sub(frag, b+1)
frag_text = App.newText(love.graphics.getFont(), frag)
frag_width = App.width(frag_text)
end
x = 25 -- new line
end
end
if #frag > 0 then
--? print('inserting '..frag..' of width '..tostring(frag_width)..'px')
table.insert(line.fragments, {data=frag, text=frag_text})
end
x = x + frag_width
end
end
function Text.textinput(t)
if love.mouse.isDown('1') then return end
if App.ctrl_down() or App.alt_down() or App.cmd_down() then return end
if Selection1.line then
Text.delete_selection()
end
local before = snapshot(Cursor1.line)
--? print(Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos, Screen_bottom1.line, Screen_bottom1.pos)
Text.insert_at_cursor(t)
if Cursor_y >= App.screen.height - Line_height then
Text.populate_screen_line_starting_pos(Cursor1.line)
Text.snap_cursor_to_bottom_of_screen()
--? print('=>', Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos, Screen_bottom1.line, Screen_bottom1.pos)
end
record_undo_event({before=before, after=snapshot(Cursor1.line)})
end
function Text.insert_at_cursor(t)
local byte_offset
if Cursor1.pos > 1 then
byte_offset = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos)
else
byte_offset = 1
end
if byte_offset == nil then
print(Cursor1.line, Cursor1.pos, byte_offset, Lines[Cursor1.line].data)
assert(false)
end
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_offset-1)..t..string.sub(Lines[Cursor1.line].data, byte_offset)
Lines[Cursor1.line].fragments = nil
Lines[Cursor1.line].screen_line_starting_pos = nil
Cursor1.pos = Cursor1.pos+1
end
-- Don't handle any keys here that would trigger love.textinput above.
function Text.keychord_pressed(chord)
--? print(chord)
--== shortcuts that mutate text
if chord == 'return' then
local before_line = Cursor1.line
local before = snapshot(before_line)
Text.insert_return()
if (Cursor_y + Line_height) > App.screen.height then
Text.snap_cursor_to_bottom_of_screen()
end
save_to_disk(Lines, Filename)
record_undo_event({before=before, after=snapshot(before_line, Cursor1.line)})
elseif chord == 'tab' then
local before = snapshot(Cursor1.line)
--? print(Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos, Screen_bottom1.line, Screen_bottom1.pos)
Text.insert_at_cursor('\t')
if Cursor_y >= App.screen.height - Line_height then
Text.populate_screen_line_starting_pos(Cursor1.line)
Text.snap_cursor_to_bottom_of_screen()
--? print('=>', Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos, Screen_bottom1.line, Screen_bottom1.pos)
end
save_to_disk(Lines, Filename)
record_undo_event({before=before, after=snapshot(Cursor1.line)})
elseif chord == 'backspace' then
if Selection1.line then
Text.delete_selection()
save_to_disk(Lines, Filename)
return
end
local before
if Cursor1.pos > 1 then
before = snapshot(Cursor1.line)
local byte_start = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos-1)
local byte_end = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos)
if byte_start then
if byte_end then
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_start-1)..string.sub(Lines[Cursor1.line].data, byte_end)
else
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_start-1)
end
Lines[Cursor1.line].fragments = nil
Cursor1.pos = Cursor1.pos-1
end
elseif Cursor1.line > 1 then
before = snapshot(Cursor1.line-1, Cursor1.line)
if Lines[Cursor1.line-1].mode == 'drawing' then
table.remove(Lines, Cursor1.line-1)
else
-- join lines
Cursor1.pos = utf8.len(Lines[Cursor1.line-1].data)+1
Lines[Cursor1.line-1].data = Lines[Cursor1.line-1].data..Lines[Cursor1.line].data
Lines[Cursor1.line-1].fragments = nil
table.remove(Lines, Cursor1.line)
end
Cursor1.line = Cursor1.line-1
end
if Text.lt1(Cursor1, Screen_top1) then
local top2 = Text.to2(Screen_top1)
top2 = Text.previous_screen_line(top2)
Screen_top1 = Text.to1(top2)
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
assert(Text.le1(Screen_top1, Cursor1))
save_to_disk(Lines, Filename)
record_undo_event({before=before, after=snapshot(Cursor1.line)})
elseif chord == 'delete' then
if Selection1.line then
Text.delete_selection()
save_to_disk(Lines, Filename)
return
end
local before
if Cursor1.pos <= utf8.len(Lines[Cursor1.line].data) then
before = snapshot(Cursor1.line)
else
before = snapshot(Cursor1.line, Cursor1.line+1)
end
if Cursor1.pos <= utf8.len(Lines[Cursor1.line].data) then
local byte_start = utf8.offset(Lines[Cursor1.line].data, pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */# This file is part of ranger, the console file manager.
# License: GNU GPL version 3, see the file "AUTHORS" for details.
# TODO: Add an optional "!" to all commands and set a flag if it's there
from __future__ import (absolute_import, division, print_function)
import os
import re
# COMPAT pylint: disable=unused-import
from collections import deque # NOQA
from ranger.api import LinemodeBase, hook_init, hook_ready, register_linemode # NOQA
# pylint: enable=unused-import
import ranger
from ranger.core.shared import FileManagerAware
from ranger.ext.lazy_property import lazy_property
_SETTINGS_RE = re.compile(r'^\s*([^\s]+?)=(.*)$')
def _alias_line(full_command, line):
return full_command + ''.join(re.split(r'(\s+)', line)[1:])
class CommandContainer(FileManagerAware):
def __init__(self):
self.commands = {}
def __getitem__(self, key):
return self.commands[key]
def alias(self, name, full_command):
cmd_name = full_command.split()[0]
try:
cmd = self.get_command(cmd_name)
except KeyError:
self.fm.notify('alias failed: No such command: {0}'.format(cmd_name), bad=True)
return None
class CommandAlias(cmd): # pylint: disable=too-few-public-methods
def __init__(self, line, *args, **kwargs):
super(CommandAlias, self).__init__(
_alias_line(self.full_command, line), *args, **kwargs)
cmd_alias = type(name, (CommandAlias, ), dict(full_command=full_command))
if issubclass(cmd_alias, FunctionCommand):
cmd_alias.based_function = name
cmd_alias.object_name = name
cmd_alias.function_name = name
self.commands[name] = cmd_alias
def load_commands_from_module(self, module):
for var in vars(module).values():
try:
if issubclass(var, Command) and var != Command \
and var != FunctionCommand:
self.commands[var.get_name()] = var
except TypeError:
pass
def load_commands_from_object(self, obj, filtr):
for attribute_name in dir(obj):
if attribute_name[0] == '_' or attribute_name not in filtr:
continue
attribute = getattr(obj, attribute_name)
if hasattr(attribute, '__call__'):
cmd = type(attribute_name, (FunctionCommand, ), dict(__doc__=attribute.__doc__))
cmd.based_function = attribute
cmd.function_name = attribute.__name__
cmd.object_name = obj.__class__.__name__
self.commands[attribute_name] = cmd
def get_command(self, name, abbrev=True):
if abbrev:
lst = [cls for cmd, cls in self.commands.items()
if cls.allow_abbrev and cmd.startswith(name) or cmd == name]
if not lst:
raise KeyError
if len(lst) == 1:
return lst[0]
if self.commands[name] in lst:
return self.commands[name]
raise ValueError("Ambiguous command")
else:
try:
return self.commands[name]
except KeyError:
return None
def command_generator(self, start):
return sorted(cmd + ' ' for cmd in self.commands if cmd.startswith(start))
class Command(FileManagerAware):
"""Abstract command class"""
name = None
allow_abbrev = True
resolve_macros = True
escape_macros_for_shell = False
quantifier = None
_shifted = 0
_setting_line = None
def __init__(self, line, quantifier=None):
self.init_line(line)
self.quantifier = quantifier
self.quickly_executed = False
def init_line(self, line):
self.line = line
self.args = line.split()
try:
self.firstpart = line[:line.rindex(' ') + 1]
except ValueError:
self.firstpart = ''
@classmethod
def get_name(cls):
classdict = cls.__mro__[0].__dict__
if 'name' in classdict and classdict['name']:
return cls.name
return cls.__name__
def execute(self):
"""Override this"""
def tab(self, tabnum):
"""Override this"""
def quick(self):
"""Override this"""
def cancel(self):
"""Override this"""
# Easy ways to get information
def arg(self, n):
"""Returns the nth space separated word"""
try:
return self.args[n]
except IndexError:
return ""
def rest(self, n):
"""Returns everything from and after arg(n)"""
got_space = True
word_count = 0
for i, char in enumerate(self.line):
if char.isspace():
if not got_space:
got_space = True
word_count += 1
elif got_space:
got_space = False
if word_count == n + self._shifted:
return self.line[i:]
return ""
def start(self, n):
"""Returns everything until (inclusively) arg(n)"""
return ' '.join(self.args[:n]) + " " # XXX
def shift(self):
del self.args[0]
self._setting_line = None
self._shifted += 1
def parse_setting_line(self):
"""
Parses the command line argument that is passed to the `:set` command.
Returns [option, value, name_complete].
Can parse incomplete lines too, and `name_complete` is a boolean
indicating whether the option name looks like it's completed or
unfinished. This is useful for generating tab completions.
>>> Command("set foo=bar").parse_setting_line()
['foo', 'bar', True]
>>> Command("set foo").parse_setting_line()
['foo', '', False]
>>> Command("set foo=").parse_setting_line()
['foo', '', True]
>>> Command("set foo ").parse_setting_line()
['foo', '', True]
>>> Command("set myoption myvalue").parse_setting_line()
['myoption', 'myvalue', True]
>>> Command("set").parse_setting_line()
['', '', False]
"""
if self._setting_line is not None:
return self._setting_line
match = _SETTINGS_RE.match(self.rest(1))
if match:
self.firstpart += match.group(1) + '='
result = [match.group(1), match.group(2), True]
else:
result = [self.arg(1), self.rest(2), ' ' in self.rest(1)]
self._setting_line = result
return result
def parse_setting_line_v2(self):
"""
Parses the command line argument that is passed to the `:set` command.
Returns [option, value, name_complete, toggle].
>>> Command("set foo=bar").parse_setting_line_v2()
['foo', 'bar', True, False]
>>> Command("set foo!").parse_setting_line_v2()
['foo', '', True, True]
"""
option, value, name_complete = self.parse_setting_line()
if len(option) >= 2 and option[-1] == '!':
toggle = True
option = option[:-1]
name_complete = True
else:
toggle = False
return [option, value, name_complete, toggle]
def parse_flags(self):
"""Finds and returns flags in the command
>>> Command("").parse_flags()
('', '')
>>> Command("foo").parse_flags()
('', '')
>>> Command("shell test").parse_flags()
('', 'test')
>>> Command("shell -t ls -l").parse_flags()
('t', 'ls -l')
>>> Command("shell -f -- -q test").parse_flags()
('f', '-q test')
>>> Command("shell -foo -bar rest of the command").parse_flags()
('foobar', 'rest of the command')
"""
flags = ""
args = self.line.split()
rest = ""
if args:
rest = self.line[len(args[0]):].lstrip()
for arg in args[1:]:
if arg == "--":
rest = rest[2:].lstrip()
break
elif len(arg) > 1 and arg[0] == "-":
rest = rest[len(arg):].lstrip()
flags += arg[1:]
else:
break
return flags, rest
@lazy_property
def log(self):
import logging
return logging.getLogger('ranger.commands.' + self.__class__.__name__)
# COMPAT: this is still used in old commands.py configs
def _tab_only_directories(self):
from os.path import dirname, basename, expanduser, join
cwd = self.fm.thisdir.path
rel_dest = self.rest(1)
# expand the tilde into the user directory
if rel_dest.startswith('~'):
rel_dest = expanduser(rel_dest)
# define some shortcuts
abs_dest = join(cwd, rel_dest)
abs_dirname = dirname(abs_dest)
rel_basename = basename(rel_dest)
rel_dirname = dirname(rel_dest)
try:
# are we at the end of a directory?
if rel_dest.endswith('/') or rel_dest == '':
_, dirnames, _ = next(os.walk(abs_dest))
# are we in the middle of the filename?
else:
_, dirnames, _ = next(os.walk(abs_dirname))
dirnames = [dn for dn in dirnames
if dn.startswith(rel_basename)]
except (OSError, StopIteration):
# os.walk found nothing
pass
else:
dirnames.sort()
# no results, return None
if not dirnames:
return
# one result. since it must be a directory, append a slash.
if len(dirnames) == 1:
return self.start(1) + join(rel_dirname, dirnames[0]) + '/'
# more than one result. append no slash, so the user can
# manually type in the slash to advance into that directory
return (self.start(1) + join(rel_dirname, dirname)
for dirname in dirnames)
def _tab_directory_content(self): # pylint: disable=too-many-locals
from os.path import dirname, basename, expanduser, join
cwd = self.fm.thisdir.path
rel_dest = self.rest(1)
# expand the tilde into the user directory
if rel_dest.startswith('~'):
rel_dest = expanduser(rel_dest)
# define some shortcuts
abs_dest = join(cwd, rel_dest)
abs_dirname = dirname(abs_dest)
rel_basename = basename(rel_dest)
rel_dirname = dirname(rel_dest)
try:
directory = self.fm.get_directory(abs_dest)
# are we at the end of a directory?
if rel_dest.endswith('/') or rel_dest == '':
if directory.content_loaded:
# Take the order from the directory object
names = [f.basename for f in directory.files]
if self.fm.thisfile.basename in names:
i = names.index(self.fm.thisfile.basename)
names = names[i:] + names[:i]
else:
# Fall back to old method with "os.walk"
_, dirnames, filenames = next(os.walk(abs_dest))
names = sorted(dirnames + filenames)
# are we in the middle of the filename?
else:
if directory.content_loaded:
# Take the order from the directory object
names = [f.basename for f in directory.files
if f.basename.startswith(rel_basename)]
if self.fm.thisfile.basename in names:
i = names.index(self.fm.thisfile.basename)
names = names[i:] + names[:i]
else:
# Fall back to old method with "os.walk"
_, dirnames, filenames = next(os.walk(abs_dirname))
names = sorted([name for name in (dirnames + filenames)
if name.startswith(rel_basename)])
except (OSError, StopIteration):
# os.walk found nothing
pass
else:
# no results, return None
if not names:
return
# one result. append a slash if it's a directory
if len(names) == 1:
path = join(rel_dirname, names[0])
slash = '/' if os.path.isdir(path) else ''
return self.start(1) + path + slash
# more than one result. append no slash, so the user can
# manually type in the slash to advance into that directory
return (self.start(1) + join(rel_dirname, name) for name in names)
def _tab_through_executables(self):
from ranger.ext.get_executables import get_executables
programs = [program for program in get_executables() if
program.startswith(self.rest(1))]
if not programs:
return
if len(programs) == 1:
return self.start(1) + programs[0]
programs.sort()
return (self.start(1) + program for program in programs)
class FunctionCommand(Command):
based_function = None
object_name = ""
function_name = "unknown"
def execute(self): # pylint: disable=too-many-branches
if not self.based_function:
return
if len(self.args) == 1:
try:
return self.based_function( # pylint: disable=not-callable
**{'narg': self.quantifier})
except TypeError:
return self.based_function() # pylint: disable=not-callable
args, keywords = list(), dict()
for arg in self.args[1:]:
equal_sign = arg.find("=")
value = arg if (equal_sign is -1) else arg[equal_sign + 1:]
try:
value = int(value)
except ValueError:
if value in ('True', 'False'):
value = (value == 'True')
else:
try:
value = float(value)
except ValueError:
pass
if equal_sign == -1:
args.append(value)
else:
keywords[arg[:equal_sign]] = value
if self.quantifier is not None:
keywords['narg'] = self.quantifier
try:
if self.quantifier is None:
return self.based_function(*args, **keywords) # pylint: disable=not-callable
else:
try:
return self.based_function(*args, **keywords) # pylint: disable=not-callable
except TypeError:
del keywords['narg']
return self.based_function(*args, **keywords) # pylint: disable=not-callable
except TypeError:
if ranger.args.debug:
raise
else:
self.fm.notify(
"Bad arguments for %s.%s: %s, %s" % (
self.object_name, self.function_name, repr(args), repr(keywords)),
bad=True,
)
if __name__ == '__main__':
import doctest
doctest.testmod()