Skip to content

Commit

Permalink
repl: improved ObjectFilenameCompleter completions ordering
Browse files Browse the repository at this point in the history
Completions are now sorted by file type (first directories, than
symbolic links and, finally, ordinary files).

Also, we have added a slash to directories completions so it works
nicely with the new enter key behaviour.

Signed-off-by: Pablo Palácios <ppalacios992@gmail.com>
  • Loading branch information
pablopalacios committed Aug 4, 2017
1 parent affcc2c commit 7f8a108
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 7 deletions.
60 changes: 60 additions & 0 deletions tests/repl/test_completers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (C) 2017 O.S. Systems Software LTDA.
# SPDX-License-Identifier: GPL-2.0

import os
import shutil
import tempfile
import unittest
from unittest.mock import Mock, patch

from uhu.repl.completers import ObjectFilenameCompleter


class ObjectFilenameCompleterTestCase(unittest.TestCase):

def test_can_set_completions_in_the_right_order(self):
# creates a base dir
base_dir = tempfile.mkdtemp(prefix='uh-completer-test-')
self.addCleanup(shutil.rmtree, base_dir)
os.chdir(base_dir)

# Set up some files
_, f1 = tempfile.mkstemp(prefix='file-1', dir=base_dir)
_, f2 = tempfile.mkstemp(prefix='file-2', dir=base_dir)

# Set up some dirs
dir1 = tempfile.mkdtemp(prefix='dir-1', dir=base_dir)
dir2 = tempfile.mkdtemp(prefix='dir-2', dir=base_dir)

# Set up some links
os.symlink(__file__, os.path.join(base_dir, 'sym1'))
os.symlink(__file__, os.path.join(base_dir, 'sym2'))

# Set up prompt toolkit document mock
document = Mock()
document.text_before_cursor = ''

# Set up completer
completer = ObjectFilenameCompleter()

# Start the test!
expected = [dir1, dir2, 'sym1', 'sym2', f1, f2]
completions = completer.get_completions(document, None)
observed = [completion.text for completion in completions]
self.assertEqual(len(observed), len(expected))

def test_can_handle_files_which_do_not_exist(self):
document = Mock()
document.text_before_cursor = 'invalid-path/invalid-file'
completer = ObjectFilenameCompleter()
completions = list(completer.get_completions(document, None))
self.assertEqual(len(completions), 0)

@patch('uhu.repl.completers.os')
def test_can_handle_any_os_error(self, mock):
mock.path.dirname.side_effect = OSError
document = Mock()
document.text_before_cursor = ''
completer = ObjectFilenameCompleter()
completions = completer.get_completions(document, None)
self.assertIsNone(completions)
85 changes: 79 additions & 6 deletions uhu/repl/completers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,89 @@

import os

from prompt_toolkit.contrib.completers import PathCompleter, WordCompleter
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.contrib.completers import WordCompleter

from ..core.object import Modes


# pylint: disable=too-few-public-methods
class ObjectFilenameCompleter(PathCompleter):

def __init__(self):
super().__init__(file_filter=os.path.isfile)
class ObjectFilenameCompleter(Completer):

DIR = 0
LINK = 1
FILE = 2

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.value = None
self.directory = None
self.prefix = None
self.filenames = None

def get_completions(self, document, complete_event):
try:
self.normalize_value(document)
self.set_base_directory()
self.set_base_filename()
self.set_filenames()
return self.all_completions()
except OSError:
pass

def normalize_value(self, document):
self.value = document.text_before_cursor

def set_base_directory(self):
dirname = os.path.dirname(self.value)
if dirname:
self.directory = os.path.dirname(os.path.join('.', self.value))
else:
self.directory = '.'

def set_base_filename(self):
self.prefix = os.path.basename(self.value)

def set_filenames(self):
try:
names = sorted([(fn, self._get_sort_key(fn))
for fn in os.listdir(self.directory)
if fn.startswith(self.prefix)])
except FileNotFoundError:
names = []
self.filenames = sorted(names, key=lambda v: v[1])

def all_completions(self):
for filename, file_type in self.filenames:
yield self.set_completion(filename, file_type)

def set_completion(self, filename, file_type):
completion = self._set_completion(file_type, filename)
kwargs = self._set_completion_kwargs(file_type, filename)
return Completion(completion, **kwargs)

def _get_sort_key(self, fn):
if os.path.isdir(fn):
return self.DIR
if os.path.islink(fn):
return self.LINK
return self.FILE

def _set_completion(self, file_type, filename):
completion = filename[len(self.prefix):]
if file_type == self.DIR:
completion += '/'
return completion

def _set_completion_kwargs(self, file_type, filename):
kwargs = {
'display': filename,
'start_position': 0,
}
if file_type == self.DIR:
kwargs['display'] = '{}/'.format(filename)
elif file_type == self.LINK:
kwargs['display'] = '{} (symbolic link)'.format(filename)
return kwargs


# pylint: disable=too-few-public-methods
Expand Down
2 changes: 1 addition & 1 deletion uhu/repl/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@


@manager.registry.add_binding(Keys.ControlD)
def ctrl_d(event):
def ctrl_d(_):
"""Ctrl D quits appliaction returning 0 to sys."""
sys.exit(0)

Expand Down

0 comments on commit 7f8a108

Please sign in to comment.