Exemplo n.º 1
0
 def get_width(self, progress_bar):
     all_lengths = [
         len('{0}'.format(c.total)) for c in progress_bar.counters
     ]
     all_lengths.append(1)
     return D.exact(max(all_lengths) * 2 + 1)
Exemplo n.º 2
0
 def get_width(self, progress_bar):
     return D.exact(1)
Exemplo n.º 3
0
 def get_width(self, progress_bar: 'ProgressBar') -> AnyDimension:
     all_lengths = [
         len('{0:>3}'.format(c.total)) for c in progress_bar.counters
     ]
     all_lengths.append(1)
     return D.exact(max(all_lengths) * 2 + 1)
Exemplo n.º 4
0
 def get_width(self, progress_bar: 'ProgressBar') -> AnyDimension:
     return D.exact(1)
Exemplo n.º 5
0
 def get_width(self, progress_bar):
     fmt = "{{:{}}}".format(self.pos_format.format(width='')).format
     lengths = (len(fmt(c.final_position)) for c in progress_bar.counters)
     return D.exact(max(lengths))
Exemplo n.º 6
0
 def get_width(self, progress_bar: "ProgressBar") -> AnyDimension:
     return D.exact(6)
Exemplo n.º 7
0
 def get_width(self, progress_bar: "Progress"):
     return D.exact(5)
Exemplo n.º 8
0
 def get_width(self, progress_bar):
     fmt = "{{:{}}}".format(self.pos_format.format(width='')).format
     lengths = [len(fmt(c.initial_position)) for c in progress_bar.counters]
     lengths += [len(fmt(c.final_position)) for c in progress_bar.counters]
     # +2 to account for - sign and space
     return D.exact(max(lengths) + 2)
 def get_width(self, progress_bar):
     return D.exact(1)
 def get_width(self, progress_bar):
     all_lengths = [len('{0}'.format(c.total)) for c in progress_bar.counters]
     all_lengths.append(1)
     return D.exact(max(all_lengths) * 2 + 1)
Exemplo n.º 11
0
    def __init__(self, multiline: FilterOrBool = True,
                 password: FilterOrBool = False,
                 focusable: FilterOrBool = True,
                 focus_on_click: FilterOrBool = False,
                 width: AnyDimension = None,
                 height: AnyDimension = None,
                 dont_extend_height: FilterOrBool = False,
                 dont_extend_width: FilterOrBool = False,
                 line_numbers: bool = False,
                 get_line_prefix: Optional[GetLinePrefixCallable] = None,
                 scrollbar: bool = False, style: str = "",
                 search_field: Optional[SearchToolbar] = None,
                 preview_search: FilterOrBool = True,
                 prompt: AnyFormattedText = "",
                 input_processors: Optional[List[Processor]] = None,
                 key_bindings: KeyBindings = None, **kwargs) -> None:

        super().__init__(multiline=multiline, password=password,
                         focusable=focusable, focus_on_click=focus_on_click,
                         width=width, height=height,
                         dont_extend_height=dont_extend_height,
                         dont_extend_width=dont_extend_width,
                         line_numbers=line_numbers,
                         get_line_prefix=get_line_prefix,
                         scrollbar=scrollbar, style=style,
                         search_field=search_field,
                         preview_search=preview_search, prompt=prompt,
                         input_processors=input_processors, **kwargs)

        if search_field is None:
            search_control = None
        elif isinstance(search_field, SearchToolbar):
            search_control = search_field.control

        if input_processors is None:
            input_processors = []

        self.control = BufferControl(
            buffer=self.buffer,
            lexer=DynamicLexer(lambda: self.lexer),
            input_processors=[
                                 ConditionalProcessor(
                                     AppendAutoSuggestion(),
                                     has_focus(self.buffer) & ~is_done
                                 ),
                                 ConditionalProcessor(
                                     processor=PasswordProcessor(),
                                     filter=to_filter(password)
                                 ),
                                 BeforeInput(prompt,
                                             style="class:text-area.prompt"),
                             ] + input_processors,
            search_buffer_control=search_control,
            preview_search=preview_search,
            focusable=focusable,
            focus_on_click=focus_on_click,
            key_bindings=key_bindings
        )

        if multiline:
            right_margins = [ScrollbarMargin(display_arrows=True)] \
                if scrollbar else []

            left_margins = [NumberedMargin()] if line_numbers else []

        else:
            height = D.exact(1)
            left_margins = []
            right_margins = []

        style = "class:text-area " + style

        self.window = Window(
            height=height,
            width=width,
            dont_extend_height=dont_extend_height,
            dont_extend_width=dont_extend_width,
            content=self.control,
            style=style,
            wrap_lines=Condition(lambda: is_true(self.wrap_lines)),
            left_margins=left_margins,
            right_margins=right_margins,
            get_line_prefix=get_line_prefix
        )
Exemplo n.º 12
0
 def get_width(self, progress_bar: "ProgressBar") -> AnyDimension:
     all_lengths = [
         len("{:>3}".format(c.total or "?")) for c in progress_bar.counters
     ]
     all_lengths.append(1)
     return D.exact(max(all_lengths) * 2 + 1)
Exemplo n.º 13
0
def browse():
    """
    A browser for the bibmanager database.
    """
    # Content of the text buffer:
    bibs = bm.load()
    keys = [bib.key for bib in bibs]
    all_compact_text = "\n".join(keys)
    all_expanded_text = "\n\n".join(bib.meta() + bib.content for bib in bibs)
    # A list object, since I want this to be a global variable
    selected_content = [None]

    lex_style = style_from_pygments_cls(
        pygments.styles.get_style_by_name(cm.get('style')))
    custom_style = Style.from_dict({
        "status": "reverse",
        "status.position": "#aaaa00",
        "status.key": "#ffaa00",
        "shadow": "bg:#440044",
        "not-searching": "#888888",
    })
    style = merge_styles([lex_style, custom_style])

    def get_menubar_text():
        return [
            ("class:status", " ("),
            ("class:status.key", "enter"),
            ("class:status", ")select entry  ("),
            ("class:status.key", "e"),
            ("class:status", ")xpand entry  ("),
            ("class:status.key", "f"),
            ("class:status", ")ind  ("),
            ("class:status.key", "s"),
            ("class:status", ")ave  ("),
            ("class:status.key", "h"),
            ("class:status", ")elp  ("),
            ("class:status.key", "q"),
            ("class:status", ")uit"),
        ]

    def get_menubar_right_text():
        """Get index of entry under cursor."""
        key = get_current_key(text_field.buffer.document, keys)
        return f" {keys.index(key) + 1} "

    def get_infobar_text():
        """Get author-year-title of entry under cursor."""
        key = get_current_key(text_field.buffer.document, keys)
        bib = bibs[keys.index(key)]
        year = '' if bib.year is None else bib.year
        title = 'NO_TITLE' if bib.title is None else bib.title
        return f"{bib.get_authors('ushort')}{year}: {title}"

    search_buffer = Buffer(completer=WordCompleter(keys),
                           complete_while_typing=False,
                           multiline=False)
    literal_search_field = SearchToolbar(
        search_buffer=search_buffer,
        forward_search_prompt="Text search: ",
        backward_search_prompt="Text search backward: ",
        ignore_case=False)

    # Entry search bar:
    authors_list = [bib.authors for bib in bibs]
    firsts = sorted(
        set([
            u.get_authors([authors[0]], format='ushort')
            for authors in authors_list if authors is not None
        ]))
    firsts = [
        '^{' + first + '}' if ' ' in first else '^' + first for first in firsts
    ]

    lasts = sorted(
        set([
            u.get_authors([author], format='ushort')
            for authors in authors_list if authors is not None
            for author in authors
        ]))
    lasts = ['{' + last + '}' if ' ' in last else last for last in lasts]

    bibkeys = [bib.key for bib in bibs]
    bibcodes = [bib.bibcode for bib in bibs if bib.bibcode is not None]
    bibyears = sorted(
        set([str(bib.year) for bib in bibs if bib.year is not None]))
    titles = [bib.title for bib in bibs]
    tags = sorted(
        set(
            itertools.chain(
                *[bib.tags for bib in bibs if bib.tags is not None])))

    key_words = {
        'author:"^"': firsts,
        'author:""': lasts,
        'year:': bibyears,
        'title:""': titles,
        'key:': bibkeys,
        'bibcode:': bibcodes,
        'tags:': tags,
    }
    completer = u.DynamicKeywordCompleter(key_words)
    suggester = u.DynamicKeywordSuggester()
    auto_suggest_bindings = load_auto_suggest_bindings()

    # Searcher:
    entry_search_buffer = Buffer(
        completer=completer,
        complete_while_typing=False,
        auto_suggest=suggester,
    )

    def get_line_prefix(lineno, wrap_count):
        return FormattedText([
            ('bold', 'Entry search: '),
        ])

    entry_search_field = Window(BufferControl(
        buffer=entry_search_buffer,
        input_processors=[AppendAutoSuggestion()],
    ),
                                get_line_prefix=get_line_prefix,
                                height=1)
    # Wrap in conditional container to display it only when focused:
    entry_search_focus = Condition(
        lambda: get_app().layout.current_window == entry_search_field)
    entry_search_container = ConditionalContainer(
        content=entry_search_field,
        filter=entry_search_focus,
    )

    text_field = TextArea(
        text=all_compact_text,
        lexer=PygmentsLexer(BibTeXLexer),
        scrollbar=True,
        line_numbers=False,
        read_only=True,
        search_field=literal_search_field,
        input_processors=[HighlightEntryProcessor()],
    )
    text_field.buffer.name = 'text_area_buffer'
    text_field.is_expanded = False
    text_field.compact_text = all_compact_text
    text_field.expanded_text = all_expanded_text
    # Shortcut to HighlightEntryProcessor:
    for processor in text_field.control.input_processors:
        if processor.__class__.__name__ == 'HighlightEntryProcessor':
            text_field.bm_processor = processor
    # Do not highlight searched text:
    sp = text_field.control.default_input_processors[0]
    sp._classname = ' '
    sp._classname_current = ' '

    menu_bar = VSplit(
        [
            Window(FormattedTextControl(get_menubar_text),
                   style="class:status"),
            Window(FormattedTextControl(get_menubar_right_text),
                   style="class:status.right",
                   width=9,
                   align=WindowAlign.RIGHT),
        ],
        height=1,
    )

    info_bar = ConditionalContainer(
        content=Window(
            content=FormattedTextControl(get_infobar_text),
            height=D.exact(1),
            style="class:status",
        ),
        filter=~entry_search_focus,
    )

    body = HSplit([
        menu_bar,
        text_field,
        literal_search_field,
        entry_search_container,
        info_bar,
    ])

    root_container = FloatContainer(
        content=body,
        floats=[
            Float(
                xcursor=True,
                ycursor=True,
                content=CompletionsMenu(max_height=16, scroll_offset=1),
            ),
        ],
    )

    # Key bindings:
    bindings = KeyBindings()

    text_focus = Condition(
        lambda: get_app().layout.current_window == text_field.window)
    dialog_focus = Condition(
        lambda: hasattr(get_app().layout.current_window, 'dialog'))

    @bindings.add("q", filter=text_focus)
    def _quit(event):
        event.app.exit()

    # Navigation:
    @bindings.add("g", filter=text_focus)
    def _go_to_first_line(event):
        event.current_buffer.cursor_position = 0

    @bindings.add("G", filter=text_focus)
    def _go_to_last_line(event) -> None:
        event.current_buffer.cursor_position = len(event.current_buffer.text)

    @bindings.add("d", filter=text_focus)
    def _scroll_down(event):
        scroll_half_page_down(event)

    @bindings.add("u", filter=text_focus)
    def _scroll_up(event):
        scroll_half_page_up(event)

    @bindings.add("n", filter=text_focus)
    def _find_next(event):
        search_state = event.app.current_search_state
        event.current_buffer.apply_search(search_state,
                                          include_current_position=False,
                                          count=event.arg)

    @bindings.add("N", filter=text_focus)
    def _find_previous(event):
        search_state = event.app.current_search_state
        event.current_buffer.apply_search(~search_state,
                                          include_current_position=False,
                                          count=event.arg)

    @bindings.add("h", filter=text_focus)
    def _show_help(event):
        show_message("Shortcuts", help_message)

    @bindings.add("f", filter=text_focus)
    def _start_literal_search(event):
        search.start_search(direction=search.SearchDirection.FORWARD)

    # TBD: Remove 't' binding no before 17/12/2022
    @bindings.add("t", filter=text_focus)
    @bindings.add("k", filter=text_focus)
    def _start_entry_search(event):
        text_field.current_key = get_current_key(event.current_buffer.document,
                                                 keys)
        event.app.layout.focus(entry_search_field)

    @bindings.add("b", filter=text_focus)
    def _open_in_browser(event):
        key = get_current_key(event.current_buffer.document, keys)
        bib = bm.find(key=key, bibs=bibs)
        if bib.adsurl is not None:
            webbrowser.open(bib.adsurl, new=2)
        else:
            show_message("Message", f"Entry '{key}' does not have an ADS url.")

    @bindings.add("c-c", filter=dialog_focus)
    def _close_dialog(event):
        get_app().layout.current_window.dialog.future.set_result(None)

    @bindings.add("s", filter=text_focus)
    def _save_selected_to_file(event):
        selected = text_field.bm_processor.selected_entries
        if len(selected) == 0:
            show_message("Message", "Nothing to save.")
            return

        async def coroutine():
            dialog = TextInputDialog(
                title="Save to File",
                label_text="\nEnter a file path or leave blank to quit "
                "and print to screen:\n(press Control-c to cancel)\n",
                completer=PathCompleter(),
            )
            path = await show_dialog_as_float(dialog)
            content = '\n\n'.join(bibs[keys.index(key)].content
                                  for key in selected)
            if path == "":
                selected_content[0] = content
                # The program termination is in TextInputDialog() since I
                # need to close this coroutine first.
                return
            if path is not None:
                try:
                    with open(path, "w") as f:
                        f.write(content)
                except IOError as e:
                    show_message("Error", str(e))

        ensure_future(coroutine())

    @bindings.add("enter", filter=text_focus)
    def _toggle_selected_entry(event):
        "Select/deselect entry pointed by the cursor."
        key = get_current_key(event.current_buffer.document, keys)
        text_field.bm_processor.toggle_selected_entry(key)

    @bindings.add("enter", filter=entry_search_focus)
    def _select_entries(event):
        "Parse the input tag text and send focus back to main text."
        # Reset tag text to '':
        doc = event.current_buffer.document
        start_pos = doc.cursor_position + doc.get_start_of_line_position()
        event.current_buffer.cursor_position = start_pos
        event.current_buffer.delete(doc.get_end_of_line_position() -
                                    doc.get_start_of_line_position())

        # Catch text and parse search text:
        matches = u.parse_search(doc.current_line)
        if len(matches) == 0:
            text_field.compact_text = all_compact_text[:]
            text_field.expanded_text = all_expanded_text[:]
            search_buffer.completer.words = keys
        else:
            text_field.compact_text = "\n".join([bib.key for bib in matches])
            text_field.expanded_text = "\n\n".join(bib.meta() + bib.content
                                                   for bib in matches)
            search_buffer.completer.words = [bib.key for bib in matches]

        # Return focus to main text:
        event.app.layout.focus(text_field.window)

        # Update main text with selected tag:
        buffer = event.current_buffer
        text_field.text = text_field.compact_text
        if text_field.current_key in search_buffer.completer.words:
            buffer_position = text_field.text.index(text_field.current_key)
        else:
            buffer_position = 0
        buffer.cursor_position = buffer_position
        text_field.is_expanded = False

    # TBD: Remove 'T' binding no before 17/12/2022
    @bindings.add("T", filter=text_focus)
    @bindings.add("K", filter=text_focus)
    def _deselect_tags(event):
        buffer = event.current_buffer
        key = get_current_key(buffer.document, keys)
        text_field.compact_text = all_compact_text[:]
        text_field.expanded_text = all_expanded_text[:]
        search_buffer.completer.words = keys
        # Update main text:
        text_field.text = text_field.compact_text
        buffer.cursor_position = buffer.text.index(key)
        text_field.is_expanded = False

    @bindings.add("e", filter=text_focus)
    def _expand_collapse_entry(event):
        "Expand/collapse current entry."
        doc = event.current_buffer.document
        key, start_end, is_expanded = get_current_key(doc,
                                                      keys,
                                                      get_start_end=True,
                                                      get_expanded=True)
        bib = bm.find(key=key, bibs=bibs)
        if is_expanded:
            # Remove blank lines around if surrounded by keys:
            start_row, _ = doc._find_line_start_index(start_end[0])
            if start_row > 0 and doc.lines[start_row - 2] in keys:
                start_end[0] -= 1
            end_row, _ = doc._find_line_start_index(start_end[1])
            if end_row < doc.line_count - 1 and doc.lines[end_row + 2] in keys:
                start_end[1] += 1
            event.app.clipboard.set_text(bib.key)
        else:
            expanded_content = bib.meta() + bib.content
            row = doc.cursor_position_row
            # Add blank lines around if surrounded by keys:
            if row > 0 and doc.lines[row - 1] != '':
                expanded_content = '\n' + expanded_content
            if row < doc.line_count - 1 and doc.lines[row + 1] != '':
                expanded_content = expanded_content + '\n'
            event.app.clipboard.set_text(expanded_content)

        text_field.read_only = False
        event.current_buffer.cursor_position = start_end[0]
        event.current_buffer.delete(count=start_end[1] - start_end[0])
        event.current_buffer.paste_clipboard_data(
            event.app.clipboard.get_data(),
            count=event.arg,
            paste_mode=PasteMode.VI_BEFORE)
        text_field.read_only = True
        if is_expanded:
            event.current_buffer.cursor_position = start_end[0]

    @bindings.add("E", filter=text_focus)
    def _expand_collapse_all(event):
        "Expand/collapse all entries."
        buffer = event.current_buffer
        key = get_current_key(buffer.document, keys)
        if text_field.is_expanded:
            text_field.text = text_field.compact_text
        else:
            text_field.text = text_field.expanded_text

        buffer.cursor_position = buffer.text.index(key)
        text_field.is_expanded = not text_field.is_expanded

    @bindings.add("o", filter=text_focus)
    def _open_pdf(event):
        buffer = event.current_buffer
        key = get_current_key(buffer.document, keys)
        bib = bm.find(key=key, bibs=bibs)

        has_pdf = bib.pdf is not None
        has_bibcode = bib.bibcode is not None
        is_missing = has_pdf and not os.path.exists(f'{u.BM_PDF()}{bib.pdf}')

        if not has_pdf and not has_bibcode:
            show_message("Message",
                         f"BibTeX entry '{key}' does not have a PDF.")
            return

        if has_pdf and not is_missing:
            pm.open(key=key)
            return

        if has_pdf and is_missing and not has_bibcode:
            show_message(
                "Message",
                f"BibTeX entry has a PDF file: {bib.pdf}, but the file "
                "could not be found.")
            return

        # Need to fetch before opening:
        async def coroutine():
            dialog = MessageDialog(
                "PDF file not found",
                "Fetch from ADS?\n(might take a few seconds ...)",
                asking=True)
            fetch = await show_dialog_as_float(dialog)
            if fetch:
                with io.StringIO() as buf, redirect_stdout(buf):
                    fetched = pm.fetch(bib.bibcode, replace=True)
                    fetch_output = buf.getvalue()

                if fetched is None:
                    show_message("PDF fetch failed", fetch_output)
                else:
                    show_message("PDF fetch succeeded.", fetch_output)
                    pm.open(key=key)

        ensure_future(coroutine())

    key_bindings = merge_key_bindings([
        auto_suggest_bindings,
        bindings,
    ])
    application = Application(
        layout=Layout(root_container, focused_element=text_field),
        key_bindings=key_bindings,
        enable_page_navigation_bindings=True,
        style=style,
        full_screen=True,
    )

    application.run()
    if selected_content[0] is not None:
        tokens = list(pygments.lex(selected_content[0], lexer=BibTeXLexer()))

        print_formatted_text(
            PygmentsTokens(tokens),
            end="",
            style=lex_style,
            #output=create_output(sys.stdout),
        )