~nhoad/roland

9a4085628c652dd81d274996b466d59f333275e2 — Nathan Hoad 2 years ago 2b190e3 master
Greatly simplify string matching, and use blocking prompts as much as possible.
1 files changed, 77 insertions(+), 127 deletions(-)

M roland/core.py
M roland/core.py => roland/core.py +77 -127
@@ 5,7 5,6 @@ import code
import collections
import datetime
import faulthandler
import fnmatch
import functools
import html
import itertools


@@ 288,15 287,6 @@ class BrowserCommands:

    @private
    def select_window(self, selected=None):
        def present_window(selected):
            try:
                win_id = name_to_id[selected]
                win = id_to_window[win_id]
            except KeyError:
                pass
            else:
                win.present()

        browsers = self.roland.get_browsers()
        name_to_id = {'%d: %s' % (i, w.get_title()): i for (i, w) in enumerate(browsers, 1)}
        id_to_window = {i: w for (i, w) in enumerate(browsers, 1)}


@@ 312,40 302,30 @@ class BrowserCommands:
            else:
                win.present()
            return True
        else:
            browsers = self.roland.get_browsers()
            name_to_id = {'%d: %s' % (i, w.get_title()): i for (i, w) in enumerate(browsers, 1)}
            id_to_window = {i: w for (i, w) in enumerate(browsers, 1)}

            self.entry_line.prompt(
                present_window, prompt="Window", force_match=True, glob=True,
                suggestions=sorted(name_to_id))
        selected = self.entry_line.blocking_prompt(
            prompt="Window", force_match=True, suggestions=sorted(name_to_id))

        try:
            win_id = name_to_id[selected]
            win = id_to_window[win_id]
        except KeyError:
            pass
        else:
            win.present()
        return True

    @private
    def open(self, *, url=None, new_window=False, background=False):
        def open_window(url):
            if background or new_window:
                if new_window:
                    log.info("Loading {} in a new window", url)
                elif background:
                    log.info("Loading {} in a background window", url)
                self.roland.new_window(url, background=background)
            else:
                log.info("Loading {}", url)
                self.webview.load_uri(url)

        if not url:
            prompt = 'open'
            if background:
                prompt += ' (new background window)'
            elif new_window:
                prompt += ' (new window)'
            self.entry_line.prompt(
                open_window, prompt=prompt, glob=True,
                suggestions=self.roland.most_popular_urls())
    def open(self, *, url, new_window=False, background=False):
        if background or new_window:
            if new_window:
                log.info("Loading {} in a new window", url)
            elif background:
                log.info("Loading {} in a background window", url)
            self.roland.new_window(url, background=background)
        else:
            open_window(url)
            log.info("Loading {}", url)
            self.webview.load_uri(url)
        return True

    @requires('SessionManager')


@@ 381,7 361,7 @@ class BrowserCommands:
                maybe_hostname = urlparse.urlparse(maybe_url).hostname

                if maybe_hostname and (' ' in maybe_hostname or '_' in maybe_hostname):
                    run_search(text)
                    run_search(text=text)
                else:
                    resolver = Gio.Resolver.get_default()
                    resolver.lookup_by_name_async(


@@ 394,24 374,23 @@ class BrowserCommands:
            elif new_window:
                prompt += ' (new window)'

            self.entry_line.prompt(
                open_or_search, prompt=prompt, glob=True,
                suggestions=self.roland.most_popular_urls())
        else:
            text = self.entry_line.blocking_prompt(
                prompt=prompt, suggestions=self.roland.most_popular_urls())

        if text:
            open_or_search(text)
        return True

    @private
    def open_modify(self, new_window=False):
        def open_window(url):
            self.open(url=url, new_window=new_window)

        prompt = 'open'
        if new_window:
            prompt += ' (new window)'

        self.entry_line.prompt(
            open_window, prompt=prompt, initial=self.webview.get_uri() or '')
        url = self.entry_line.blocking_prompt(
            prompt=prompt, initial=self.webview.get_uri() or '')
        if url:
            self.open(url=url, new_window=new_window)
        return True

    @private


@@ 442,31 421,21 @@ class BrowserCommands:
        self.roland.add_close_history(self.webview.get_uri(), self.get_serialised_session_state())

    @private
    def change_user_agent(self, user_agent=None):
        def change_user_agent(user_agent):
            if not user_agent:
                return
    def change_user_agent(self):
        user_agents = [self.roland.config.default_user_agent] + self.roland.hooks('user_agent_choices', default=[])
        user_agent = self.entry_line.blocking_prompt(
            prompt="User Agent", suggestions=user_agents)

        if user_agent:
            for browser in self.roland.get_browsers():
                browser.web_view.get_settings().props.user_agent = user_agent

        if user_agent is None:
            user_agents = [self.roland.config.default_user_agent] + self.roland.hooks('user_agent_choices', default=[])
            self.entry_line.prompt(change_user_agent, prompt="User Agent", suggestions=user_agents)
        else:
            change_user_agent(user_agent)
        return True

    @private
    def search(self, text=None, new_window=False, background=False):
        def search(text):
            search_url = self.roland.config.search_page.format(text)
            url = self.roland.hooks('search_url', text, default=None) or search_url
            self.open(url=url, new_window=new_window, background=background)

        if text is None:
            self.entry_line.prompt(search, prompt='Search')
        else:
            search(text)
    def search(self, *, text, new_window=False, background=False):
        search_url = self.roland.config.search_page.format(text)
        url = self.roland.hooks('search_url', text, default=None) or search_url
        self.open(url=url, new_window=new_window, background=background)
        return True

    def back(self):


@@ 561,7 530,7 @@ class BrowserCommands:
                for s in click_map.keys()], key=lambda s: int(s.split(':')[0]))
            self.entry_line.prompt(
                functools.partial(open_callback or open_link, click_map), prompt=prompt, cancel=self.remove_overlay,
                suggestions=suggestions, force_match=True, beginning=False)
                suggestions=suggestions, force_match=True)

        click_map = {}



@@ 582,34 551,31 @@ class BrowserCommands:
        return True

    @private
    def search_page(self, text=None, forwards=True, case_insensitive=None):
        def search_page(text):
            nonlocal case_insensitive
            if case_insensitive is None:
                case_insensitive = text.lower() == text
    def search_page(self, forwards=True, case_insensitive=None):
        text = self.entry_line.blocking_prompt(prompt='Search page')
        if not text:
            return True

            finder = self.webview.get_find_controller()
        if case_insensitive is None:
            case_insensitive = text.lower() == text

            if text == '':
                finder.search_finish()
                return
        finder = self.webview.get_find_controller()

            self.search_forwards = forwards
        if text == '':
            finder.search_finish()
            return

            options = WebKit2.FindOptions.WRAP_AROUND
            if not forwards:
                options |= WebKit2.FindOptions.BACKWARDS
        self.search_forwards = forwards

            if case_insensitive:
                options |= WebKit2.FindOptions.CASE_INSENSITIVE
        options = WebKit2.FindOptions.WRAP_AROUND
        if not forwards:
            options |= WebKit2.FindOptions.BACKWARDS

            max_count = 1000  # FIXME: configurable?
            finder.search(text, options, max_count)
        if case_insensitive:
            options |= WebKit2.FindOptions.CASE_INSENSITIVE

        if text is None:
            self.entry_line.prompt(search_page, prompt='Search page')
        else:
            search_page(text)
        max_count = 1000  # FIXME: configurable?
        finder.search(text, options, max_count)
        return True

    @private


@@ 724,17 690,16 @@ class BrowserCommands:
            self.roland.notify("No downloads in progress")
            return

        def cancel_download(key):
            try:
                download = self.roland.downloads[key]
            except KeyError:
                self.roland.notify("No download by that name")
            else:
                download.cancel()
        key = self.entry_line.blocking_prompt(
            prompt="Cancel download", force_match=True,
            suggestions=list(self.roland.downloads.keys()))

        self.entry_line.prompt(
            cancel_download, prompt="Cancel download", force_match=True,
            glob=True, suggestions=list(self.roland.downloads.keys()))
        try:
            download = self.roland.downloads[key]
        except KeyError:
            self.roland.notify("No download by that name")
        else:
            download.cancel()

        return True



@@ 904,17 869,14 @@ class EntryLine(Gtk.VBox):

        return result

    def prompt(self, callback, suggestions=None, force_match=False,
               glob=False, prompt='', initial='', cancel=None,
               case_sensitive=False, beginning=True, private=False):
    def prompt(
            self, callback, suggestions=None, force_match=False, prompt='',
            initial='', cancel=None, private=False):
        self.callback = callback
        self.suggestions = suggestions or []
        self.force_match = force_match
        self.glob = glob
        self.lock_suggestions = False
        self.cancel = cancel
        self.case_sensitive = case_sensitive
        self.beginning = beginning
        self.input.set_visibility(not private)

        self.label.set_markup('{}:'.format(prompt))


@@ 934,7 896,7 @@ class EntryLine(Gtk.VBox):

    def filter_suggestions(self, suggestions, prompt):
        result = self.blocking_prompt(
            prompt=prompt, suggestions=suggestions, glob=False, beginning=False)
            prompt=prompt, suggestions=suggestions)
        if result is None:
            return []
        return [s for s in suggestions if result.casefold() in s.casefold()]


@@ 962,21 924,10 @@ class EntryLine(Gtk.VBox):
        self.get_toplevel().set_focus(None)

    def add_completions(self):
        t = self.input.get_text()
        if self.glob:
            entries = fnmatch.filter(self.suggestions, '*{}*'.format(t))
        else:
            if self.case_sensitive:
                f = lambda a: a
            else:
                f = str.casefold

            if self.beginning:
                condition = str.startswith
            else:
                condition = str.__contains__
        t = self.input.get_text().casefold()

            entries = [e for e in self.suggestions if condition(f(e), f(t))]
        # FIXME: make this smarter, spitting on words and stuff
        entries = [e for e in self.suggestions if t in e.casefold()]

        for entry in reversed(entries[:20]):
            # FIXME: highlight matching portion


@@ 1525,16 1476,15 @@ class BrowserView(BrowserCommands):
            return True

    def prompt_command(self):
        def run_command(text):
        text = self.entry_line.blocking_prompt(
            prompt='command', force_match=True,
            suggestions=self.roland.get_commands())
        if text:
            if not text.strip():
                return
            command = list(shlex.split(text))
            command_name, args = command[0], command[1:]
            self.run_command(command_name, *args)

        self.entry_line.prompt(
            run_command, prompt='command', force_match=True,
            suggestions=self.roland.get_commands(), beginning=False)
        return True

    def run_command(self, name, *args):