Example #1
0
def setup():
    displayer = WelcomeMessageDisplayer()
    get_tab_manager().bind('<Configure>', displayer.update_wraplen, add=True)
    utils.bind_with_data(get_tab_manager(),
                         '<<NewTab>>',
                         displayer.on_new_tab,
                         add=True)
Example #2
0
def setup():
    # the action's binding feature cannot be used because then typing
    # a '#' outside the main text widget inserts a # to the main widget
    actions.add_command("Edit/Comment Block",
                        comment_or_uncomment,
                        filetype_names=filetype_names)
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
Example #3
0
def on_new_tab(tab: tabs.Tab) -> None:
    if isinstance(tab, tabs.FileTab):
        underliner = _Underliner(tab.textwidget)
        utils.bind_with_data(tab,
                             "<<SetUnderlines>>",
                             underliner.set_underlines,
                             add=True)
Example #4
0
def setup():
    actions.add_command("Edit/Find and Replace",
                        find,
                        '<Control-f>',
                        tabtypes=[tabs.FileTab])
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
    get_tab_manager().bind('<<NotebookTabChanged>>', on_tab_changed, add=True)
Example #5
0
def setup() -> None:
    # trigger <<UnderlinerHidePopup>> when text widget goes invisible (e.g. switching tabs)
    get_main_window().event_add('<<UnderlinerHidePopup>>', '<Unmap>')

    # and when the entire porcupine window loses input focus (binding here to avoid unbinding)
    get_main_window().bind('<FocusOut>', hide_all_message_labels, add=True)

    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
Example #6
0
def setup() -> None:
    # the action's binding feature cannot be used because then typing
    # a '#' outside the main text widget inserts a # to the main widget
    menubar.get_menu("Edit").add_command(
        label="Comment Block", command=comment_or_uncomment_in_current_tab)
    menubar.set_enabled_based_on_tab(
        "Edit/Comment Block", (lambda tab: isinstance(tab, tabs.FileTab)))
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
Example #7
0
def on_new_tab(event: utils.EventWithData) -> None:
    tab = event.data_widget()
    if isinstance(tab, tabs.FileTab):
        underliner = _Underliner(tab.textwidget)
        utils.bind_with_data(tab,
                             '<<SetUnderlines>>',
                             underliner.set_underlines,
                             add=True)
Example #8
0
def setup() -> None:
    # load_filetypes() got already called in setup_argument_parser()
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
    get_main_window().bind('<<PluginsLoaded>>',
                           open_files_specified_on_command_line,
                           add=True)
    add_default_filetype_setting()
    configure_filetypes_kwargs()
    create_filetypes_menu()
Example #9
0
def test_bind_with_data_string():
    # i don't know why the main window works better with this than a
    # temporary tkinter.Frame()
    events = []
    utils.bind_with_data(get_main_window(), "<<Asd>>", events.append, add=True)
    get_main_window().event_generate("<<Asd>>", data="hello")

    [event] = events
    assert event.widget is get_main_window()
    assert event.data_string == "hello"
Example #10
0
def test_bind_with_data_string(porcusession, mocker):
    # i don't know why the main window works better with this than a
    # temporary tkinter.Frame()
    events = []
    utils.bind_with_data(get_main_window(), '<<Asd>>', events.append, add=True)
    get_main_window().event_generate('<<Asd>>', data='hello')

    [event] = events
    assert event.widget is get_main_window()
    assert event.data_string == 'hello'
Example #11
0
 def __init__(self, tab: tabs.FileTab) -> None:
     self._tab = tab
     self._orig_cursorpos: Optional[str] = None
     self._id_counter = itertools.count()
     self._waiting_for_response_id: Optional[int] = None
     self.popup = _Popup()
     utils.bind_with_data(
         tab,
         '<<AutoCompletionResponse>>',
         lambda event: self.receive_completions(event.data_class(Response)),
         add=True)
Example #12
0
def test_bind_with_data_class(porcusession):
    events = []
    utils.bind_with_data(
        get_main_window(), '<<DataclassAsd>>', events.append, add=True)
    get_main_window().event_generate(
        '<<DataclassAsd>>', data=Bar(foos=[Foo(message='abc', num=123)]))

    [event] = events
    bar = event.data_class(Bar)
    [foo] = bar.foos
    assert foo.message == 'abc'
    assert foo.num == 123
Example #13
0
def test_bind_with_data_class():
    events = []
    utils.bind_with_data(get_main_window(), "<<DataclassAsd>>", events.append, add=True)
    get_main_window().event_generate(
        "<<DataclassAsd>>", data=Bar(foos=[Foo(message="abc", num=123)])
    )

    [event] = events
    bar = event.data_class(Bar)
    [foo] = bar.foos
    assert foo.message == "abc"
    assert foo.num == 123
def text_and_events(porcusession):
    text = tkinter.Text(get_main_window())
    text.config(undo=True)  # must be before track_changes()
    track_changes(text)

    # peers can mess things up
    create_peer_widget(text, tkinter.Text(get_main_window()))

    events = []
    utils.bind_with_data(text, '<<ContentChanged>>', events.append, add=True)
    yield (text, events)
    assert not events
Example #15
0
def on_new_tab(tab: tabs.Tab) -> None:
    if isinstance(tab, tabs.FileTab):
        # needed because pygments_lexer might change
        def on_lexer_changed(junk: object = None) -> None:
            assert isinstance(tab, tabs.FileTab)  # f u mypy
            highlighter.set_lexer(tab.settings.get("pygments_lexer", LexerMeta)())

        highlighter = Highlighter(tab.textwidget)
        tab.bind("<<TabSettingChanged:pygments_lexer>>", on_lexer_changed, add=True)
        on_lexer_changed()
        utils.bind_with_data(tab.textwidget, "<<ContentChanged>>", highlighter.on_change, add=True)
        utils.add_scroll_command(
            tab.textwidget, "yscrollcommand", debounce(tab, highlighter.highlight_visible, 100)
        )
        highlighter.highlight_visible()
Example #16
0
def test_bind_with_data(porcusession):
    ran = 0

    # i don't know why the main window works better with this than a
    # temporary tkinter.Frame()
    def cb(event):
        assert event.widget is get_main_window()
        assert event.data_tuple(int, str, int) == (1, 'asd asd', 3)
        nonlocal ran
        ran += 1

    utils.bind_with_data(get_main_window(), '<<Asd>>', cb, add=True)
    get_main_window().event_generate('<<Asd>>',
                                     data=utils.create_tcl_list(
                                         [1, 'asd asd', 3]))
    assert ran == 1
Example #17
0
def setup() -> None:
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)

    menubar.get_menu("Run").add_command(label="Compile",
                                        command=partial(
                                            do_something, 'compile'))
    menubar.get_menu("Run").add_command(label="Run",
                                        command=partial(do_something, 'run'))
    menubar.get_menu("Run").add_command(label="Compile and Run",
                                        command=partial(
                                            do_something, 'compilerun'))
    menubar.get_menu("Run").add_command(label="Lint",
                                        command=partial(
                                            do_something, 'compilerun'))

    # TODO: disable the menu items when they don't correspond to actual commands
    for label in {"Compile", "Run", "Compile and Run", "Lint"}:
        menubar.set_enabled_based_on_tab(
            f"Run/{label}", (lambda tab: isinstance(tab, tabs.FileTab)))
Example #18
0
def on_new_tab(event: utils.EventWithData) -> None:
    tab = event.data_widget()
    if not isinstance(tab, tabs.FileTab):
        return

    tab.settings.add_option('autocomplete_chars', [], type=List[str])

    completer = AutoCompleter(tab)

    # no idea why backspace has to be bound separately
    utils.bind_with_data(tab.textwidget,
                         '<Key>',
                         completer.on_any_key,
                         add=True)
    tab.textwidget.bind('<BackSpace>', completer.on_any_key, add=True)

    utils.bind_tab_key(tab.textwidget, completer.on_tab, add=True)
    tab.textwidget.bind('<Return>', completer.on_enter, add=True)
    tab.textwidget.bind('<Escape>', completer.on_escape, add=True)
    tab.textwidget.bind('<Prior>', completer.popup.on_page_up_down, add=True)
    tab.textwidget.bind('<Next>', completer.popup.on_page_up_down, add=True)
    tab.textwidget.bind('<Up>', completer.popup.on_arrow_key_up_down, add=True)
    tab.textwidget.bind('<Down>',
                        completer.popup.on_arrow_key_up_down,
                        add=True)
    completer.popup.treeview.bind('<Button-1>',
                                  (lambda event: completer._accept()),
                                  add=True)

    # avoid weird corner cases
    tab.winfo_toplevel().bind('<FocusOut>',
                              (lambda event: completer._reject()),
                              add=True)
    tab.textwidget.bind(
        # any mouse button
        '<Button>',
        (lambda event: completer._reject()),
        add=True)

    tab.bind('<Destroy>', (lambda event: completer.popup.toplevel.destroy()),
             add=True)
Example #19
0
def on_new_tab(tab: tabs.Tab) -> None:
    if not isinstance(tab, tabs.FileTab):
        return

    tab.settings.add_option("autocomplete_chars", [], List[str])

    completer = AutoCompleter(tab)

    # no idea why backspace has to be bound separately
    utils.bind_with_data(tab.textwidget,
                         "<Key>",
                         completer.on_any_key,
                         add=True)
    tab.textwidget.bind("<BackSpace>", completer.on_any_key, add=True)

    utils.bind_tab_key(tab.textwidget, completer.on_tab, add=True)
    tab.textwidget.bind("<Return>", completer.on_enter, add=True)
    tab.textwidget.bind("<Escape>", completer.on_escape, add=True)
    tab.textwidget.bind("<Prior>", completer.popup.on_page_up_down, add=True)
    tab.textwidget.bind("<Next>", completer.popup.on_page_up_down, add=True)
    tab.textwidget.bind("<Up>", completer.popup.on_arrow_key_up_down, add=True)
    tab.textwidget.bind("<Down>",
                        completer.popup.on_arrow_key_up_down,
                        add=True)
    completer.popup.treeview.bind("<Button-1>",
                                  (lambda event: completer._accept()),
                                  add=True)

    # avoid weird corner cases
    tab.winfo_toplevel().bind("<FocusOut>",
                              (lambda event: completer._reject()),
                              add=True)
    tab.textwidget.bind(
        # any mouse button
        "<Button>",
        lambda event: completer._reject(),
        add=True,
    )

    tab.bind("<Destroy>", (lambda event: completer.popup.toplevel.destroy()),
             add=True)
Example #20
0
def setup():
    var = tkinter.StringVar()

    # this initial value isn't shown anywhere, it just needs to be set to
    # something to avoid an error in actions.add_choice
    var.set(filetypes.get_all_filetypes()[0].name)

    var.trace('w', functools.partial(var_value_to_tab, var))
    get_tab_manager().bind('<<NotebookTabChanged>>',
                           functools.partial(tab_filetype_to_var, var),
                           add=True)
    utils.bind_with_data(get_tab_manager(),
                         '<<NewTab>>',
                         functools.partial(on_new_tab, var),
                         add=True)

    actions.add_choice(
        'Filetypes',
        [filetype.name for filetype in filetypes.get_all_filetypes()],
        var=var,
        tabtypes=[tabs.FileTab])
Example #21
0
def setup():
    window = get_main_window()
    menubar = MenuManager()
    window['menu'] = menubar.main_menu

    utils.bind_with_data(window,
                         '<<NewAction>>',
                         menubar.on_new_action,
                         add=True)
    utils.bind_with_data(window,
                         '<<ActionEnabled>>',
                         (lambda event: menubar.on_enable_disable(event.data)),
                         add=True)
    utils.bind_with_data(window,
                         '<<ActionDisabled>>',
                         (lambda event: menubar.on_enable_disable(event.data)),
                         add=True)

    for action in actions.get_all_actions():
        menubar.setup_action(action)
Example #22
0
def setup():
    window = get_main_window()
    menubar = MenubarHandler()
    window['menu'] = menubar.main_menu

    utils.bind_with_data(window,
                         '<<NewAction>>',
                         menubar.on_new_action,
                         add=True)
    utils.bind_with_data(window,
                         '<<ActionEnabled>>',
                         functools.partial(menubar.on_enable_disable,
                                           'normal'),
                         add=True)
    utils.bind_with_data(window,
                         '<<ActionDisabled>>',
                         functools.partial(menubar.on_enable_disable,
                                           'disabled'),
                         add=True)

    for action in actions.get_all_actions():
        menubar.setup_action(action)
Example #23
0
def setup() -> None:
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
Example #24
0
def setup() -> None:
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
    settings.add_option('autocomplete_popup_width', 500)
    settings.add_option('autocomplete_popup_height', 200)
    settings.add_option('autocomplete_divider_pos', 200)
Example #25
0
def _add_any_action(path,
                    kind,
                    callback_or_choices,
                    binding,
                    var,
                    *,
                    filetype_names=None,
                    tabtypes=None):
    if path.startswith('/') or path.endswith('/'):
        raise ValueError("action paths must not start or end with /")
    if filetype_names is not None and tabtypes is not None:
        # python raises TypeError when it comes to invalid arguments
        raise TypeError("only one of filetype_names and tabtypes can be used")
    if path in _actions:
        raise RuntimeError("there's already an action with path %r" % path)

    # event_generate must be before setting action.enabled, this way
    # plugins get a chance to do something to the new action before it's
    # disabled
    action = _Action(path, kind, callback_or_choices, binding, var)
    _actions[path] = action
    porcupine.get_main_window().event_generate('<<NewAction>>', data=path)

    if tabtypes is not None or filetype_names is not None:
        if tabtypes is not None:
            tabtypes = tuple(
                # None is the only type(None) object
                type(None) if cls is None else cls for cls in tabtypes)

            def enable_or_disable(junk_event=None):
                tab = porcupine.get_tab_manager().select()
                action.enabled = isinstance(tab, tabtypes)

        if filetype_names is not None:
            # the noqa comment is needed because flake8 thinks this is
            # a "redefinition of unused 'enable_or_disable'"
            def enable_or_disable(junk_event=None):  # noqa
                tab = porcupine.get_tab_manager().select()
                if isinstance(tab, tabs.FileTab):
                    action.enabled = tab.filetype.name in filetype_names
                else:
                    action.enabled = False

            def on_new_tab(event):
                tab = event.data_widget
                if isinstance(tab, tabs.FileTab):
                    tab.bind('<<FiletypeChanged>>',
                             enable_or_disable,
                             add=True)

            utils.bind_with_data(porcupine.get_tab_manager(),
                                 '<<NewTab>>',
                                 on_new_tab,
                                 add=True)

        enable_or_disable()
        porcupine.get_tab_manager().bind('<<NotebookTabChanged>>',
                                         enable_or_disable,
                                         add=True)

    # TODO: custom keyboard bindings with a config file or something
    if binding is not None:
        assert kind in {'command', 'yesno'}, repr(kind)

        def bind_callback(event):
            if action.enabled:
                if kind == 'command':
                    action.callback()
                if kind == 'yesno':
                    action.var.set(not action.var.get())
                # try to allow binding keys that are used for other
                # things by default
                return 'break'

        # TODO: display a warning if it's already bound
        porcupine.get_main_window().bind(binding, bind_callback, add=True)

    return action
Example #26
0
def _add_any_action(path,
                    kind,
                    callback_or_choices,
                    binding,
                    var,
                    *,
                    filetype_names=None,
                    tabtypes=None):
    if path.startswith('/') or path.endswith('/'):
        raise ValueError("action paths must not start or end with /")
    if filetype_names is not None and tabtypes is not None:
        # python raises TypeError when it comes to invalid arguments
        raise TypeError("only one of filetype_names and tabtypes can be used")
    if path in _actions:
        raise RuntimeError("there's already an action with path %r" % path)

    # event_generate must be before setting action.enabled, this way
    # plugins get a chance to do something to the new action before it's
    # disabled
    action = _Action(path, kind, callback_or_choices, binding, var)
    _actions[path] = action
    porcupine.get_main_window().event_generate('<<NewAction>>', data=path)

    if tabtypes is not None or filetype_names is not None:
        if tabtypes is not None:
            tabtypes = tuple(
                # None is the only type(None) object
                type(None) if cls is None else cls for cls in tabtypes)

            def enable_or_disable(junk_event=None):
                tab = porcupine.get_tab_manager().select()
                action.enabled = isinstance(tab, tabtypes)

        if filetype_names is not None:
            # the noqa comment is needed because flake8 thinks this is
            # a "redefinition of unused 'enable_or_disable'"
            def enable_or_disable(junk_event=None):  # noqa
                tab = porcupine.get_tab_manager().select()
                if isinstance(tab, tabs.FileTab):
                    action.enabled = tab.filetype.name in filetype_names
                else:
                    action.enabled = False

            def on_new_tab(event):
                tab = event.data_widget
                if isinstance(tab, tabs.FileTab):
                    tab.bind('<<FiletypeChanged>>',
                             enable_or_disable,
                             add=True)

            utils.bind_with_data(porcupine.get_tab_manager(),
                                 '<<NewTab>>',
                                 on_new_tab,
                                 add=True)

        enable_or_disable()
        porcupine.get_tab_manager().bind('<<NotebookTabChanged>>',
                                         enable_or_disable,
                                         add=True)

    # TODO: custom keyboard bindings with a config file or something
    if binding is not None:
        assert kind in {'command', 'yesno'}, repr(kind)

        def bind_callback(event):
            if action.enabled:
                if kind == 'command':
                    action.callback()
                if kind == 'yesno':
                    action.var.set(not action.var.get())
                # try to allow binding keys that are used for other
                # things by default
                return 'break'

        # TODO: display a warning if it's already bound?
        widget = porcupine.get_main_window()  # any widget would do
        widget.bind_all(binding, bind_callback, add=True)

        # text widgets are tricky, by default they insert a newline on ctrl+o,
        # and i discovered how it works in a Tcl session:
        #
        #    wish8.6 [~]bind Text <Control-o>
        #
        #        if {!$tk_strictMotif} {
        #    	      %W insert insert \n
        #    	      %W mark set insert insert-1c
        #        }
        #
        # preventing that is simple as binding it to nothing, and then they'll
        # do the bind_all thing as usual:
        #
        #    wish8.6 [~]bind Text <Control-o>
        #    wish8.6 [~]bind Text <Control-o>     ;# empty string is returned
        #    wish8.6 [~]
        #
        # this is done to all action bindings instead of just <Control-o> to
        # avoid any issues with other bindings
        widget.tk.call('bind', 'Text', binding, '')

    return action