# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import curses.ascii
from collections import deque
from inspect import isfunction, getargspec
from ranger.ext.tree import Tree
from ranger.ext.direction import Direction
from ranger.ext.keybinding_parser import parse_keybinding, \
DIRKEY, ANYKEY, PASSIVE_ACTION
MAX_ALIAS_RECURSION = 20
FUNC = 'func'
DIRECTION = 'direction'
DIRARG = 'dir'
ALIASARG = 'alias'
def to_string(i):
"""convert a ord'd integer to a string"""
try:
return chr(i)
except ValueError:
return '?'
def is_ascii_digit(n):
return n >= 48 and n <= 57
class CommandArgs(object):
"""The arguments which are passed to a keybinding function"""
def __init__(self, fm, widget, keybuffer):
self.fm = fm
self.wdg = widget
self.keybuffer = keybuffer
self.n = keybuffer.quant
self.direction = keybuffer.directions and keybuffer.directions[0] or None
self.directions = keybuffer.directions
self.keys = str(keybuffer)
self.matches = keybuffer.matches
self.match = keybuffer.matches and keybuffer.matches[0] or None
self.binding = keybuffer.command
@staticmethod
def from_widget(widget):
return CommandArgs(widget.fm, \
widget, widget.env.keybuffer)
class KeyMap(Tree):
"""Contains a tree with all the keybindings"""
def map(self, *args, **keywords):
if keywords:
return self._add_binding(*args, **keywords)
firstarg = args[-1]
if isfunction(firstarg):
keywords[FUNC] = firstarg
return self._add_binding(*args[:-1], **keywords)
def decorator_function(func):
keywords = {FUNC:func}
self.map(*args, **keywords)
return func
return decorator_function
__call__ = map
def _add_binding(self, *keys, **actions):
assert keys
bind = Binding(keys, actions)
for key in keys:
self.set(parse_keybinding(key), bind)
def unmap(self, *keys):
for key in keys:
self.unset(parse_keybinding(key))
def __getitem__(self, key):
return self.traverse(parse_keybinding(key))
class KeyMapWithDirections(KeyMap):
def __init__(self, *args, **keywords):
Tree.__init__(self, *args, **keywords)
self.directions = KeyMap()
def merge(self, other):
assert hasattr(other, 'directions'), 'Merging with wrong type?'
Tree.merge(self, other)
Tree.merge(self.directions, other.directions)
def dir(self, *args, **keywords):
if ALIASARG in keywords:
self.directions.map(*args, **keywords)
else:
self.directions.map(*args, dir=Direction(**keywords))
class KeyManager(object):
def __init__(self, keybuffer, contexts):
self._keybuffer = keybuffer
self._list_of_contexts = contexts
self.clear()
def clear(self):
self.contexts = dict()
for context in self._list_of_contexts:
self.contexts[context] = KeyMapWithDirections()
def map(self, context, *args, **keywords):
self.get_context(context).map(*args, **keywords)
def dir(self, context, *args, **keywords):
self.get_context(context).dir(*args, **keywords)
def unmap(self, context, *args, **keywords):
self.get_context(context).unmap(*args, **keywords)
def merge_all(self, keymapwithdirection):
for context, keymap in self.contexts.items():
keymap.merge(keymapwithdirection)
def get_context(self, context):
assert isinstance(context, str)
assert context in self.contexts, "no such context: " + context
return self.contexts[context]
__getitem__ = get_context
def use_context(self, context):
context = self.get_context(context)
if self._keybuffer.keymap is not context:
self._keybuffer.assign(context, context.directions)
self._keybuffer.clear()
class Binding(object):
"""The keybinding object"""
def __init__(self, keys, actions):
assert hasattr(keys, '__iter__')
assert isinstance(actions, dict)
self.actions = actions
try:
self.function = self.actions[FUNC]
except KeyError:
self.function = None
try:
self.direction = self.actions[DIRARG]
except KeyError:
self.direction = None
<void test_trace_check_compares() {
trace("test layer") << "foo" << end();
CHECK_TRACE_CONTENTS("test layer: foo");
}
void test_trace_check_ignores_other_layers() {
trace("test layer 1") << "foo" << end();
trace("test layer 2") << "bar" << end();
CHECK_TRACE_CONTENTS("test layer 1: foo");
CHECK_TRACE_DOESNT_CONTAIN("test layer 2: foo");
}
void test_trace_check_ignores_leading_whitespace() {
trace("test layer 1") << " foo" << end();
CHECK_EQ(trace_count("test layer 1", /*too little whitespace*/"foo"), 1);
CHECK_EQ(trace_count("test layer 1", /*too much whitespace*/" foo"), 1);
}
void test_trace_check_ignores_other_lines() {
trace("test layer 1") << "foo" << end();
trace("test layer 1") << "bar" << end();
CHECK_TRACE_CONTENTS("test layer 1: foo");
}
void test_trace_check_ignores_other_lines2() {
trace("test layer 1") << "foo" << end();
trace("test layer 1") << "bar" << end();
CHECK_TRACE_CONTENTS("test layer 1: bar");
}
void test_trace_ignores_trailing_whitespace() {
trace("test layer 1") << "foo\n" << end();
CHECK_TRACE_CONTENTS("test layer 1: foo");
}
void test_trace_ignores_trailing_whitespace2() {
trace("test layer 1") << "foo " << end();
CHECK_TRACE_CONTENTS("test layer 1: foo");
}
void test_trace_orders_across_layers() {
trace("test layer 1") << "foo" << end();
trace("test layer 2") << "bar" << end();
trace("test layer 1") << "qux" << end();
CHECK_TRACE_CONTENTS("test layer 1: footest layer 2: bartest layer 1: qux");
}
void test_trace_supports_count() {
trace("test layer 1") << "foo" << end();
trace("test layer 1") << "foo" << end();
CHECK_EQ(trace_count("test layer 1", "foo"), 2);
}
void test_trace_supports_count2() {
trace("test layer 1") << "foo" << end();
trace("test layer 1") << "bar" << end();
CHECK_EQ(trace_count("test layer 1"), 2);
}
void test_trace_count_ignores_trailing_whitespace() {
trace("test layer 1") << "foo\n" << end();
CHECK_EQ(trace_count("test layer 1", "foo"), 1);
}
// pending: DUMP tests
// pending: readable_contents() adds newline if necessary.
// pending: raise also prints to stderr.
// pending: raise doesn't print to stderr if Hide_errors is set.
// pending: raise doesn't have to be saved if Hide_errors is set, just printed.
// pending: raise prints to stderr if Trace_stream is NULL.
// pending: raise prints to stderr if Trace_stream is NULL even if Hide_errors is set.
// can't check trace because trace methods call 'split'
void test_split_returns_at_least_one_elem() {
vector<string> result = split("", ",");
CHECK_EQ(result.size(), 1);
CHECK_EQ(result.at(0), "");
}
void test_split_returns_entire_input_when_no_delim() {
vector<string> result = split("abc", ",");
CHECK_EQ(result.size(), 1);
CHECK_EQ(result.at(0), "abc");
}
void test_split_works() {
vector<string> result = split("abc,def", ",");
CHECK_EQ(result.size(), 2);
CHECK_EQ(result.at(0), "abc");
CHECK_EQ(result.at(1), "def");
}
void test_split_works2() {
vector<string> result = split("abc,def,ghi", ",");
CHECK_EQ(result.size(), 3);
CHECK_EQ(result.at(0), "abc");
CHECK_EQ(result.at(1), "def");
CHECK_EQ(result.at(2), "ghi");
}
void test_split_handles_multichar_delim() {
vector<string> result = split("abc,,def,,ghi", ",,");
CHECK_EQ(result.size(), 3);
CHECK_EQ(result.at(0), "abc");
CHECK_EQ(result.at(1), "def");
CHECK_EQ(result.at(2), "ghi");
}
void test_trim() {
CHECK_EQ(trim(""), "");
CHECK_EQ(trim(" "), "");
CHECK_EQ(trim(" "), "");
CHECK_EQ(trim("a"), "a");
CHECK_EQ(trim(" a"), "a");
CHECK_EQ(trim(" a"), "a");
CHECK_EQ(trim(" ab"), "ab");
CHECK_EQ(trim("a "), "a");
CHECK_EQ(trim("a "), "a");
CHECK_EQ(trim("ab "), "ab");
CHECK_EQ(trim(" a "), "a");
CHECK_EQ(trim(" a "), "a");
CHECK_EQ(trim(" ab "), "ab");
}