0

I am making a special Text widget with tkinter that is supposed to have working indentation. To get the indentation to work I binded the function that puts down the tabs to the Enter button. This binding worked fine before with the bind going after the actual typed character but it won't with this function. Can someone help me out?

Working bind:

def double_parentheses(self, event):
        main_text_box_anchor = str(self.text.index("insert")).split(".")
        self.text.insert(INSERT, ")")
        self.text.mark_set(INSERT, str(main_text_box_anchor[0]) + "." + 
        str(int(main_text_box_anchor[1])))
#later in the code
scroll.text.bind("(", scroll.double_parentheses)

This puts the parentheses in order with the insert in the middle of them

Not working:

def new_line_indent(self, event):
        line = self.text.get("insert linestart", "insert lineend")
        editable_line = str(line)
        if editable_line != "":
            if editable_line[-1] == ":":
                if "\t" in editable_line:
                    for i in range(editable_line.count("\t")):
                        self.text.insert("insert", "\t")
                else:    
                    self.text.insert("insert", "\t")
            elif "\t" in editable_line:
                for i in range(editable_line.count("\t")):
                    self.text.insert("insert", "\t")
#Later in the code
scroll.text.bind("<Return>", scroll.new_line_indent)

This puts the tabs in but it does it BEFORE the new line is created and I can't figure out why. What am I doing wrong?

TRCK
  • 231
  • 3
  • 12

2 Answers2

0

This puts the tabs in but it does it BEFORE the new line is created and I can't figure out why.

The short answer is that your binding happens before the built-in key bindings. Thus, your function is called before the newline is actually inserted by tkinter.

You should have your code insert the newline, perform your other actions, then return "break" to prevent the default behavior from happening.

For a more thorough explanation of how bindings are processed, see this answer

Here's a working example:

def new_line_indent(self, event):
    # get the text of the current line
    line = self.text.get("insert linestart", "insert lineend")

    # insert the newline
    self.text.insert("insert", "\n")

    # insert indentation if line ends with ":"
    if line and line[-1] == ":":

        # get current indentation, then insert it
        leading_whitespace = self.get_leading_whitespace(line)
        self.text.insert("insert", leading_whitespace)

        # add an additional level of indentation
        self.text.insert("insert", "\t")

    # since we already inserted the newline, make sure that
    # the default bindings do not
    return "break"

def get_leading_whitespace(self, line):
    n = len(line) - len(line.lstrip())
    return line[:n]
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • I'm also having a problem with binding a single character to two functions which causes it to be delayed until the next event with that button – TRCK Apr 07 '22 at 18:05
  • How do I force it to type in the newline before a certain function? – TRCK Apr 07 '22 at 20:14
  • @TRCK: the same way you insert anything else. `self.text.insert("insert", "\n")` – Bryan Oakley Apr 07 '22 at 20:55
  • Yes but then it skips a line. How do I make the newline come first so that it doesn't make two lines where the user has to go back up? – TRCK Apr 07 '22 at 23:53
  • @TRCK: I don't know how to answer your question. I don't think it's possible to "skip a line" - the newline will go exactly where you put it. – Bryan Oakley Apr 08 '22 at 00:07
  • So how would I do this? – TRCK Apr 08 '22 at 00:35
  • The first argument to the `insert` method is the location where the character will be inserted. The index `"insert"` instructs tkinter to insert it at the current insertion cursor. – Bryan Oakley Apr 08 '22 at 00:51
0

Other than the solution suggested by Bryan, you can bind <KeyRelease-Return> instead of <Return>. Then the callback will be executed after the newline is added:

    def new_line_indent(self, event):
        # get previous line
        line = self.text.get("insert-1c linestart", "insert-1c lineend")
        editable_line = str(line)
        if editable_line != "":
            for i in range(editable_line.count("\t")):
                self.text.insert("insert", "\t")
            if editable_line[-1] == ":":
                self.text.insert("insert", "\t")

...
#Later in the code
scroll.text.bind("<KeyRelease-Return>", scroll.new_line_indent)
acw1668
  • 40,144
  • 5
  • 22
  • 34
  • Thank you so much for your help! This works perfectly! I do have one question however. When I use the code provided it definitely works and does it well but there seems to be a small bit of lag after the new line is inserted before the tabs are added. This lag is big enough to be visible. Any thoughts on how I can cut down on this lag? Again, thank you so much for your help! – TRCK Apr 09 '22 at 20:49