Skip to content

Commit

Permalink
Merge pull request #23 from rosshemsley/use_fish_style_completion
Browse files Browse the repository at this point in the history
Use LCS for completion, update Readme
  • Loading branch information
rosshemsley committed Dec 6, 2015
2 parents 67673e2 + 8171795 commit 4059844
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 23 deletions.
37 changes: 14 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
iOpener
=======

A Sublime Text 3 package to make locating and opening files much faster, by
opening from path instead of using the gui interface. Behaviour mostly emulates
emacs' find-file. With auto-completion, directory listings and history.
A Sublime Text 2/3 package to make finding and opening files less painful.
Keep your hands on the keyboard with smart auto completion, history, and directory listings.

Use
---
Tap `super+o` or `cntl-o` as usual (or use the command pallete, and select the
command `Find File`). You will be greeted with an input panel that allows you to
type in file paths.
Usage
-----
Use `cmd + o` or `cntl + o` as usual (or choose `Find File` in the command pallete.)

- Pressing tab will cause completion to occur.
- Pressing tab will cause smart completion to occur (hint: `f1` can complete to `filename1`!)

- Double-tapping tab will allow you to search a current directory.
- Double-pressing tab will give you the directory listing

- Up/down allow you to navigate history.
- Up/down allow you to navigate previously entered paths

- Attempting to open a path that doesn't exist will give a message asking
whether or not to create the path.
- Opening a path that doesn't exist will create a new file or directory

![demo](https://raw.github.com/rosshemsley/iOpener/screenshots/demo.gif)

Note
----
- This plugin will take over your default 'open file' shortcut. If you want to
use your system file-open dialog, use `super+shift+o` or `ctrl+shift+o`
depending on your OS.
NB
--
- This plugin will take over your default 'open file' shortcut. To
use your system default, use `cmd + shift + o` or `ctrl + shift + o`

- The open panel will not show unless a window is open. You will need to use
`cntl+n` or `super+n` to open a new window first.
`cntl + n` or `cmd + n` to open a new window first


Installing
----------
Installation is easiest using Package Control. In the Command Pallette, select
"Install Package" and then select "iOpener".

Credits
-------
I looked at lots of other people's code to write this, including in particular
SublimeMRU and FuzzyFileNav. Thanks for your inspiration!

Bugs
----
Expand Down
85 changes: 85 additions & 0 deletions matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ class COMPLETION_TYPE:


def complete_path(filename, directory_listing, case_sensitive=False):
lcs_completion = get_lcs_completion_or_none(filename, directory_listing)

if lcs_completion is not None:
return lcs_completion, COMPLETION_TYPE.Complete

matches = get_matches(filename, directory_listing, case_sensitive)

if len(matches) > 1:
Expand All @@ -34,11 +39,91 @@ def longest_completion(filename, matches):
return filename + commonprefix(matches)[len(filename):]


def get_lcs_completion_or_none(filename, directory_listing):
"""
If there is a unique way to complete the path such that the LCS is the same
as the query string, return that (similar to the way Fish shell works)
"""
completion = None

for candidate in directory_listing:
if lcs(filename, candidate) == filename:
if completion is not None:
return None
else:
completion = candidate

return completion


def lcs(A, B):
"""
Taken and adapted from
http://rosettacode.org/wiki/Longest_common_subsequence#Dynamic_Programming_7
"""
lengths = [[0 for j in range(len(B) + 1)] for i in range(len(A) + 1)]

for i, x in enumerate(A):
for j, y in enumerate(B):
if x == y:
lengths[i + 1][j + 1] = lengths[i][j] + 1
else:
lengths[i + 1][j + 1] = max(lengths[i + 1][j], lengths[i][j + 1])

result = ""
x, y = len(A), len(B)
while x != 0 and y != 0:
if lengths[x][y] == lengths[x-1][y]:
x -= 1
elif lengths[x][y] == lengths[x][y-1]:
y -= 1
else:
result = A[x-1] + result
x -= 1
y -= 1
return result


##
# Unit tests
##


class TestLCSCompletion(TestCase):
def test1(self):
directory_listing = [
'filename1',
'filename2',
'test',
]

output1 = get_lcs_completion_or_none('file1', directory_listing)
self.assertEqual('filename1', output1)

output2 = get_lcs_completion_or_none('file2', directory_listing)
self.assertEqual('filename2', output2)

output3 = get_lcs_completion_or_none('tst', directory_listing)
self.assertEqual('test', output3)

output4 = get_lcs_completion_or_none('tes', directory_listing)
self.assertEqual('test', output4)

output5 = get_lcs_completion_or_none('django', directory_listing)
self.assertIsNone(output5)


class TestLCS(TestCase):
def test1(self):
self.assertEqual('hel', lcs('hello', 'heal'))
self.assertEqual('oe', lcs('hope', 'oe'))
self.assertEqual('', lcs('this', 'xyz'))
self.assertEqual('bcd', lcs('abcdefg', 'bcd'))
self.assertEqual('test', lcs('test', 'test'))
self.assertEqual('rd', lcs('read', 'rd'))
self.assertEqual('round', lcs('round', 'arounded'))


class TestCompletion(TestCase):
def test1(self):
filename = 'test'
Expand Down

0 comments on commit 4059844

Please sign in to comment.