Skip to content

Commit

Permalink
Enable filtering in file preview
Browse files Browse the repository at this point in the history
Resolves #90
  • Loading branch information
Heiko Nickerl committed Jun 25, 2018
1 parent efff1f2 commit aba1953
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 88 deletions.
4 changes: 3 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## Unreleased
## v0.14.0
##### Features
- Filtering in file preview is now possible
##### Bugfixes
- Fix rendering of tabs in filepreview
- Sodalite-open now handles a broader array of desktop entries
Expand Down
2 changes: 1 addition & 1 deletion sodalite/core/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __init__(self, path: str, access_history: List[int] = None, parent: 'Entry'
self.realpath = path
self._executable = None
self._readable = None
self._content = None
self._content: List[str] = None

def chdir(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion sodalite/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from core.entry import Entry
from util import environment

VERSION = 'sodalite v0.13.6'
VERSION = 'sodalite v0.14.0'


logger = logging.getLogger(__name__)
Expand Down
76 changes: 9 additions & 67 deletions sodalite/ui/filepreview.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,25 @@
import logging

import pygments
from pygments import lexers, token
from pygments.lexers.shell import BashLexer
from urwid import Text

from ui import theme, graphics
from ui import graphics
from ui.entrylist import List
from ui.viewmodel import ViewModel

logger = logging.getLogger(__name__)


class FilePreview(List):
def __init__(self, model):
super().__init__()
self.model = model
self.content = None
self.model: ViewModel = model
self.model.register(self)

def on_update(self):
with graphics.DRAW_LOCK:
self.body.clear()
content = self.model.file_content
if content:
lexer = find_lexer(self.model.current_entry.path, content)
logger.info("Viewing file content - using {} for highlighting".format(type(lexer).__name__))
tokens = list(lexer.get_tokens(self.model.file_content))
# tabs are not rendered correctly, replace them with spaces
tokens = replace_tabs(tokens)
self.body.extend([Text(line) for line in bold_headings(inject_linenumbers(tokens))])
if self.model.filtered_file_content and self.model.filtered_file_content != self.content:
self.content = self.model.filtered_file_content
with graphics.DRAW_LOCK:
self.body.clear()
self.body.extend([Text(line.numbered_content) for line in self.content])
self.focus_position = 0


def replace_tabs(tokens):
return [(attr, line.replace("\t", " ")) for attr, line in tokens]


def find_lexer(filename: str, content: str):
try:
lexer = lexers.guess_lexer_for_filename(filename, content, stripnl=False,
ensurenl=False)
logger.debug('Detected lexer by filename')
except pygments.util.ClassNotFound:
try:
lexer = lexers.guess_lexer(content, stripnl=False, ensurenl=False)
logger.debug('Detected lexer by file content')
except pygments.util.ClassNotFound:
lexer = BashLexer(stripnl=False, ensurenl=False)
logger.debug('Using fallback lexer')
return lexer


def inject_linenumbers(tokens):
line = []
pos = 0
line.append((theme.line_number, u"{:>2} ".format(pos + 1)))
for attr, token in tokens:
while '\n' in token:
index = token.index('\n')
begin = token[:index]
token = token[index + 1:]
if begin:
line.append((attr, begin))
pos += 1
yield line
line = [(theme.line_number, u'{:>2} '.format(pos + 1))]
if token:
line.append((attr, token))
if len(line) > 1:
yield line


def bold_headings(lines):
for line in lines:
heading = False
new_line = []
for attr, word in line:
if attr is token.Generic.Heading or attr is token.Generic.Subheading:
heading = True
if heading and attr in token.Text:
attr = 'bold'
new_line.append((attr, word))
yield new_line
83 changes: 83 additions & 0 deletions sodalite/ui/highlighting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import logging
from typing import List, Pattern

import pygments
from pygments import lexers, token
from pygments.lexers.shell import BashLexer

from core.entry import Entry
from ui import theme

logger = logging.getLogger(__name__)


class HighlightedLine:
def __init__(self, content, linenumber):
self.content = self.bold_headings(content)
self.linenumber = linenumber
formatted_linenumber = [(theme.line_number, u'{:>2} '.format(linenumber))]
self.numbered_content = [formatted_linenumber] + content

def bold_headings(self, content):
heading = False
new_content = []
for attr, word in content:
if attr is token.Generic.Heading or attr is token.Generic.Subheading:
heading = True
if heading and attr in token.Text:
attr = 'bold'
new_content.append((attr, word))
return new_content

def matches(self, pattern: Pattern):
if not pattern.pattern:
return True
for attr, word in self.content:
if pattern.search(word):
return True

def __str__(self):
return f"[{self.linenumber}: {self.content}]"


def compute_highlighting(entry: Entry) -> List[HighlightedLine]:
lexer = find_lexer(entry.path, entry.content)
logger.info("Viewing file content - using {} for highlighting".format(type(lexer).__name__))
content = entry.content.replace('\t', " ")
tokens = list(lexer.get_tokens(content))
return line_per_line(tokens)


def find_lexer(filename: str, content: str):
try:
lexer = lexers.guess_lexer_for_filename(filename, content, stripnl=False,
ensurenl=False)
logger.debug('Detected lexer by filename')
except pygments.util.ClassNotFound:
try:
lexer = lexers.guess_lexer(content, stripnl=False, ensurenl=False)
logger.debug('Detected lexer by file content')
except pygments.util.ClassNotFound:
lexer = BashLexer(stripnl=False, ensurenl=False)
logger.debug('Using fallback lexer')
return lexer


def line_per_line(tokens) -> List[HighlightedLine]:
line = []
pos = 0
for attr, token in tokens:
while '\n' in token:

index = token.index('\n')
begin = token[:index]
token = token[index + 1:]
if begin:
line.append((attr, begin))
pos += 1
yield HighlightedLine(line, pos)
line = []
if token:
line.append((attr, token))
if len(line) > 1:
yield HighlightedLine(line, pos)
42 changes: 24 additions & 18 deletions sodalite/ui/viewmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from core.entry import Entry
from core.navigator import Navigator
from ui import highlighting
from ui.highlighting import HighlightedLine
from util.observer import Observable

logger = logging.getLogger(__name__)
Expand All @@ -21,8 +23,8 @@ def __init__(self, navigator: Navigator):
super().__init__()
self.mode = Mode.NORMAL
self.current_entry: Entry = None
self._unprocessed_children = None
self.file_content = None
self.file_content: List[HighlightedLine] = None
self.filtered_file_content: List[HighlightedLine] = None
self._filter_string = ""
self.navigator = navigator
self._show_hidden_files = True
Expand All @@ -31,32 +33,36 @@ def __init__(self, navigator: Navigator):

def on_update(self):
self.current_entry = self.navigator.current_entry
self._unprocessed_children = self.current_entry.children
if self.current_entry.is_plain_text_file():
self.file_content = self.current_entry.content
self.file_content = list(highlighting.compute_highlighting(self.current_entry))
else:
self.file_content = None

self._filter_string = ""
self.process()

def process(self):
entries = list(self._unprocessed_children)
entries = self.filter_regexp(entries)
entries = self.filter_hidden_files(entries)
entries = sort(entries)
self.entries = entries
self.notify_all()
if self.current_entry.is_plain_text_file():
self.filtered_file_content = self.filter_file_content()
self.notify_all()
else:
entries = list(self.current_entry.children)
entries = self.filter_regexp(entries)
entries = self.filter_hidden_files(entries)
entries = sort(entries)
self.entries = entries
self.notify_all()

def filter_regexp(self, entries: List[Entry]) -> List[Entry]:
if self.filter_string[-1:] == "\\":
return entries
filtered = []
p = re.compile(self.filter_string, re.IGNORECASE)
for entry in entries:
if p.search(entry.name):
filtered.append(entry)
return filtered
p = self.get_filter_pattern()
return [entry for entry in entries if p.search(entry.name)]

def filter_file_content(self):
pattern = self.get_filter_pattern()
return [line for line in self.file_content if line.matches(pattern)]

def get_filter_pattern(self):
return re.compile(self.filter_string, re.IGNORECASE)

def filter_hidden_files(self, entries: List[Entry]) -> List[Entry]:
if self.show_hidden_files:
Expand Down

0 comments on commit aba1953

Please sign in to comment.