0

I'm making an HTML editor application with Python tkinter. This application uses autocomplete so users can write HTML code more easily. Example: if a user types <h1>, the script will add </h1> right after it. The issue is when I click "enter" and then "backspace". The scripts adds another end tag after the previous one. How can I fix this? My code:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename
from tkinter import messagebox
import webbrowser
import os

window = tk.Tk()
window.title("HTML Editor")
window.configure(bg="grey")
window.state("zoomed")
title = tk.Label(window, text="HTML Editor", font=("Arial Rounded MT Bold", 40, "underline"), bg="grey")
title.place(x=400, y=20)
copy_path = 0
count2 = 0


def open_file():
    file_path = askopenfilename(filetypes=[("HTML Files", "*.html"), ("All Files", "*.*")])

    if not file_path:
        return

    text_box.delete("1.0", tk.END)
    file_func()

    with open(file_path, "r") as file_read:
        text = file_read.read()
        text_box.insert(tk.END, text)
    window.title(f"HTML Editor - {file_path}")


def save_file():
    file_path = asksaveasfilename(defaultextension="txt", filetypes=[("HTML Files", "*.html"), ("All Files", "*.*")])

    if not file_path:
        return

    with open(file_path, "w") as file_write:
        text = text_box.get(1.0, tk.END)
        file_write.write(text)
    window.title(f"HTML Editor - {file_path}")
    file_func()


def run_code():
    window_title = window.title()
    index = window_title.index("-") + 2
    window_title = window_title[index::]

    with open(window_title, "w") as path:
        content = text_box.get("1.0", tk.END)
        path.write(content)

    basename = os.path.basename(window_title)
    new_file = open(basename, "w")
    new_file.write(content)
    new_file.close()
    webbrowser.open_new_tab("file:///" + os.getcwd() + "/" + basename)


def get_stringvar(event):
    global count2
    sv.set(text_box.get("1.0", tk.END))
    content = sv.get()
    keys = ["Return", "Up", "Down", "Left", "Right"]
    count2 = len(content.splitlines())

    if event.keysym in keys:
        return

    for line in content.splitlines():
        if "/" not in line and line != "<!DOCTYPE html>":
            if ("<" and ">" in line) and line.index(">") + 1 == len(line):
                index = line.index("<")
                index2 = line.index(">") + 1
                new_line = "<" + "/" + line[index + 1:index2 - 1] + ">"
                text_box.insert(tk.END, new_line)
                line2 = content.splitlines().index(line) + 1
                text_box.mark_set("insert", "%d.%d" % (line2, index2))


def on_closing():
    if window.title() != "HTML Editor":
        window_title = window.title()
        index = window_title.index("-") + 2
        window_title = window_title[index::]
        file = open(window_title, "w")
        file.write(text_box.get("1.0", tk.END))
    quit()


def file_func():
    frame.pack(side="left", fill="y")
    text_box.pack(side="left", fill="both", expand=True)
    scroll_bar.pack(side="left", fill="y")
    run_b.pack(padx=50, pady=(100, 5), anchor="n")


sv = tk.StringVar()
create = tk.Button(window, text="Create a new file", width=17, height=3, font=("Arial Rounded MT Bold", 20),
                   command=save_file)
create.place(x=420, y=200)
open_e = tk.Button(window, text="Open an existing file", width=17, height=3, font=("Arial Rounded MT Bold", 20),
                   command=open_file)
open_e.place(x=420, y=350)

window.protocol("WM_DELETE_WINDOW", on_closing)
frame = tk.Frame(window, bd=2, relief="raised")

text_box = tk.Text(window, font=("Courier New", 10), fg="black")
text_box.bind("<KeyRelease>", get_stringvar)

scroll_bar = tk.Scrollbar(window, command=text_box.yview)

run_b = tk.Button(frame, text="Run", width=6, height=2, bg="white", command=run_code)

text_box.configure(yscrollcommand=scroll_bar.set)

window.mainloop()

Updated code (Thanks, @Matiiss!):

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename
from tkinter import messagebox
import webbrowser
import os

window = tk.Tk()
window.title("HTML Editor")
window.configure(bg="grey")
window.state("zoomed")
title = tk.Label(window, text="HTML Editor", font=("Arial Rounded MT Bold", 40, "underline"), bg="grey")
title.place(x=400, y=20)
copy_path = 0


def open_file():
    file_path = askopenfilename(filetypes=[("HTML Files", "*.html"), ("All Files", "*.*")])

    if not file_path:
        return

    text_box.delete("1.0", tk.END)
    file_func()

    with open(file_path, "r") as file_read:
        text = file_read.read()
        text_box.insert(tk.END, text)
    window.title(f"HTML Editor - {file_path}")


def save_file():
    file_path = asksaveasfilename(defaultextension="txt", filetypes=[("HTML Files", "*.html"), ("All Files", "*.*")])

    if not file_path:
        return

    with open(file_path, "w") as file_write:
        text = text_box.get(1.0, tk.END)
        file_write.write(text)
    window.title(f"HTML Editor - {file_path}")
    file_func()


def run_code():
    window_title = window.title()
    index = window_title.index("-") + 2
    window_title = window_title[index::]

    with open(window_title, "w") as path:
        content = text_box.get("1.0", tk.END)
        path.write(content)

    basename = os.path.basename(window_title)
    new_file = open(basename, "w")
    new_file.write(content)
    new_file.close()
    webbrowser.open_new_tab("file:///" + os.getcwd() + "/" + basename)


def get_stringvar(event):
    sv.set(text_box.get("1.0", tk.END))
    content = sv.get()
    keys = ["Return", "Up", "Down", "Left", "Right"]

    if event.keysym in keys:
        return

    line = content.splitlines()[-1]
    if "/" not in line and line != "<!DOCTYPE html>":
        if ("<" in line and ">" in line) and line.index(">") + 1 == len(line):
            index = line.index("<")
            index2 = line.index(">") + 1
            new_line = "<" + "/" + line[index + 1:index2 - 1] + ">"
            text_box.insert(tk.END, new_line)
            line2 = content.splitlines().index(line) + 1
            text_box.mark_set("insert", "%d.%d" % (line2, index2))


def on_closing():
    if window.title() != "HTML Editor":
        window_title = window.title()
        index = window_title.index("-") + 2
        window_title = window_title[index::]
        file = open(window_title, "w")
        file.write(text_box.get("1.0", tk.END))
    quit()


def file_func():
    frame.pack(side="left", fill="y")
    text_box.pack(side="left", fill="both", expand=True)
    scroll_bar.pack(side="left", fill="y")
    run_b.pack(padx=50, pady=(100, 5), anchor="n")


sv = tk.StringVar()
create = tk.Button(window, text="Create a new file", width=17, height=3, font=("Arial Rounded MT Bold", 20),
                   command=save_file)
create.place(x=420, y=200)
open_e = tk.Button(window, text="Open an existing file", width=17, height=3, font=("Arial Rounded MT Bold", 20),
                   command=open_file)
open_e.place(x=420, y=350)

window.protocol("WM_DELETE_WINDOW", on_closing)
frame = tk.Frame(window, bd=2, relief="raised")

text_box = tk.Text(window, font=("Courier New", 10), fg="black")
text_box.bind("<KeyRelease>", get_stringvar)

scroll_bar = tk.Scrollbar(window, command=text_box.yview)

run_b = tk.Button(frame, text="Run", width=6, height=2, bg="white", command=run_code)

text_box.configure(yscrollcommand=scroll_bar.set)

window.mainloop()

The changes I've made:

I replaced for line in content.splitlines() with line = content.splitlines()[-1] and changed if ("<" in line and ">" in line) to if ("<" in line and ">" in line). Both of these changes were made in the get_stringvar function.

Roni
  • 597
  • 5
  • 21
  • 2
    as far as I know then this: `if ("<" and ">" in line)` will eval to True even if `"<"` is not in line – Matiiss Aug 15 '21 at 18:57
  • @Matiiss, I've tried printing something inside that if statement and found out that it only evaluates to true when "<" and ">" are actually in that line – Roni Aug 15 '21 at 19:15
  • 2
    but it should eval to True also only if `">"` is in line, `"<"` is not necessary to be in line and I just tested that so I know for sure, I think you are looking for this: `if ("<" in line and ">" in line):` – Matiiss Aug 15 '21 at 19:18
  • @Matiiss, I think you're right. I'm gonna try it now. – Roni Aug 15 '21 at 19:20
  • I agree with @Matiiss (for python 3.9.5), use `if ("<" in line) and (">" in line)`. – TheLizzard Aug 15 '21 at 19:20
  • @Matiiss, it worked! Thank you so much! I didn't imagine such a small change can fix the whole thing – Roni Aug 15 '21 at 19:23
  • @Roni well, me neither, I just noticed and pointed that out – Matiiss Aug 15 '21 at 19:24
  • @Matiiss, I have another issue now. The for loop in the `get_stringvar` function is causing the same problem to occur. So I decided to replace it with `line = content.splitlines()[-1]`, but it completely disables the autocomplete. What can I do to fix this? – Roni Aug 15 '21 at 19:33
  • btw you can simply do this: `content = text_box.get('1.0', 'end')`, you don't need to use a `StringVar()`, second you will probably need to use the for loop, tho I can suggest that you detect the current line by using the cursors index: https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-index.html and then get the current line and then try your autocomplete on the current line, it should also be a bit more faster (prob unnoticable for small texts) – Matiiss Aug 15 '21 at 19:42
  • @Matiiss, can you show me an example relevant to my code of how to detect the current line by using the cursor's index? – Roni Aug 15 '21 at 19:51
  • @Roni I can't write an answer because this question is closed, you can maybe ask another question about the current issue just be maybe more specific and provide a [mre] – Matiiss Aug 15 '21 at 20:06
  • @Matiiss, ok I'll ask a new question and try my best to be more specific – Roni Aug 15 '21 at 20:08
  • @Matiiss I posted my question – Roni Aug 15 '21 at 20:21

0 Answers0