From 993a03a4ed10a868706e45f5ef02c988e3b71de8 Mon Sep 17 00:00:00 2001 From: Billy Date: Fri, 21 Jul 2023 01:29:12 +0800 Subject: [PATCH] chore: Update the codebase (unclean) --- cupcake/__init__.py | 9 +- cupcake/config/languages/cpp.py | 188 ------ cupcake/config/languages/language.py | 5 - cupcake/editor/__init__.py | 121 ++-- cupcake/editor/autocomplete/__init__.py | 68 +-- cupcake/editor/autocomplete/item.py | 93 +-- cupcake/editor/autocomplete/kind.py | 41 ++ .../autocomplete/{itemkinds.py => kinds.py} | 46 +- cupcake/editor/events.py | 13 - cupcake/editor/find_replace/__init__.py | 68 --- cupcake/editor/find_replace/button.py | 41 -- cupcake/editor/find_replace/container.py | 68 --- cupcake/editor/find_replace/entrybox.py | 39 -- cupcake/editor/find_replace/find_replace.py | 226 ------- cupcake/editor/find_replace/findbox.py | 34 -- cupcake/editor/find_replace/replacebox.py | 25 - cupcake/editor/find_replace/results.py | 21 - cupcake/editor/find_replace/toggle.py | 52 -- cupcake/editor/find_replace/togglew.py | 61 -- cupcake/editor/highlighter.py | 71 ++- cupcake/editor/language/__init__.py | 1 - cupcake/editor/language/languages/__init__.py | 14 - cupcake/editor/language/languages/cpp.py | 5 - cupcake/editor/language/syntax.py | 24 - cupcake/editor/language/test.py | 44 -- cupcake/editor/linenumbers/__init__.py | 91 +-- cupcake/editor/linenumbers/breakpoint.py | 5 +- cupcake/editor/minimap.py | 25 +- cupcake/editor/scrollbar.py | 67 -- cupcake/editor/syntax.py | 9 + cupcake/editor/text.py | 575 ++++++++++++------ cupcake/editor/textw.py | 86 --- cupcake/utils/__init__.py | 28 + cupcake/utils/bubble.py | 30 + cupcake/utils/button.py | 13 + cupcake/utils/buttonsentry.py | 39 ++ cupcake/utils/canvas.py | 11 + cupcake/utils/codicon.py | 525 ++++++++++++++++ cupcake/utils/colorizer.py | 11 + cupcake/utils/entry.py | 21 + cupcake/utils/filetype.py | 11 + cupcake/utils/frame.py | 11 + cupcake/utils/icon.py | 17 + cupcake/utils/iconbutton.py | 37 ++ cupcake/utils/iconlabelbutton.py | 77 +++ cupcake/utils/label.py | 48 ++ cupcake/utils/menubutton.py | 11 + cupcake/utils/scrollableframe.py | 45 ++ cupcake/utils/scrollbar.py | 10 + cupcake/utils/shortcut.py | 24 + cupcake/utils/text.py | 11 + cupcake/utils/toplevel.py | 13 + cupcake/utils/tree.py | 89 +++ 53 files changed, 1751 insertions(+), 1567 deletions(-) delete mode 100644 cupcake/config/languages/cpp.py delete mode 100644 cupcake/config/languages/language.py create mode 100644 cupcake/editor/autocomplete/kind.py rename cupcake/editor/autocomplete/{itemkinds.py => kinds.py} (70%) delete mode 100644 cupcake/editor/events.py delete mode 100644 cupcake/editor/find_replace/__init__.py delete mode 100644 cupcake/editor/find_replace/button.py delete mode 100644 cupcake/editor/find_replace/container.py delete mode 100644 cupcake/editor/find_replace/entrybox.py delete mode 100644 cupcake/editor/find_replace/find_replace.py delete mode 100644 cupcake/editor/find_replace/findbox.py delete mode 100644 cupcake/editor/find_replace/replacebox.py delete mode 100644 cupcake/editor/find_replace/results.py delete mode 100644 cupcake/editor/find_replace/toggle.py delete mode 100644 cupcake/editor/find_replace/togglew.py delete mode 100644 cupcake/editor/language/__init__.py delete mode 100644 cupcake/editor/language/languages/__init__.py delete mode 100644 cupcake/editor/language/languages/cpp.py delete mode 100644 cupcake/editor/language/syntax.py delete mode 100644 cupcake/editor/language/test.py delete mode 100644 cupcake/editor/scrollbar.py create mode 100644 cupcake/editor/syntax.py delete mode 100644 cupcake/editor/textw.py create mode 100644 cupcake/utils/__init__.py create mode 100644 cupcake/utils/bubble.py create mode 100644 cupcake/utils/button.py create mode 100644 cupcake/utils/buttonsentry.py create mode 100644 cupcake/utils/canvas.py create mode 100644 cupcake/utils/codicon.py create mode 100644 cupcake/utils/colorizer.py create mode 100644 cupcake/utils/entry.py create mode 100644 cupcake/utils/filetype.py create mode 100644 cupcake/utils/frame.py create mode 100644 cupcake/utils/icon.py create mode 100644 cupcake/utils/iconbutton.py create mode 100644 cupcake/utils/iconlabelbutton.py create mode 100644 cupcake/utils/label.py create mode 100644 cupcake/utils/menubutton.py create mode 100644 cupcake/utils/scrollableframe.py create mode 100644 cupcake/utils/scrollbar.py create mode 100644 cupcake/utils/shortcut.py create mode 100644 cupcake/utils/text.py create mode 100644 cupcake/utils/toplevel.py create mode 100644 cupcake/utils/tree.py diff --git a/cupcake/__init__.py b/cupcake/__init__.py index e9c49d9..397905d 100644 --- a/cupcake/__init__.py +++ b/cupcake/__init__.py @@ -1,3 +1,10 @@ -__version__ = '0.5.1' +__version__ = '0.7.0' +__version_info__ = tuple([ int(num) for num in __version__.split('.')]) + +# For tests to run successfully +import sys +from os.path import abspath, dirname, join +sys.path.append(abspath(join(dirname(__file__), '.'))) + from .editor import Editor diff --git a/cupcake/config/languages/cpp.py b/cupcake/config/languages/cpp.py deleted file mode 100644 index 31271c1..0000000 --- a/cupcake/config/languages/cpp.py +++ /dev/null @@ -1,188 +0,0 @@ -from .language import Language - - -class CPP(Language): - keywords = [ - "abstract", - "amp", - "array", - "auto", - "bool", - "break", - "case", - "catch", - "char", - "class", - "const", - "constexpr", - "const_cast", - "continue", - "cpu", - "decltype", - "default", - "delegate", - "delete", - "do", - "double", - "dynamic_cast", - "each", - "else", - "enum", - "event", - "explicit", - "export", - "extern", - "false", - "final", - "finally", - "float", - "for", - "friend", - "gcnew", - "generic", - "goto", - "if", - "in", - "initonly", - "inline", - "int", - "interface", - "interior_ptr", - "internal", - "literal", - "long", - "mutable", - "namespace", - "new", - "noexcept", - "nullptr", - "__nullptr", - "operator", - "override", - "partial", - "pascal", - "pin_ptr", - "private", - "property", - "protected", - "public", - "ref", - "register", - "reinterpret_cast", - "restrict", - "return", - "safe_cast", - "sealed", - "short", - "signed", - "sizeof", - "static", - "static_assert", - "static_cast", - "struct", - "switch", - "template", - "this", - "thread_local", - "throw", - "tile_static", - "true", - "try", - "typedef", - "typeid", - "typename", - "union", - "unsigned", - "using", - "virtual", - "void", - "volatile", - "wchar_t", - "where", - "while", - - "_asm", - "_based", - "_cdecl", - "_declspec", - "_fastcall", - "_if_exists", - "_if_not_exists", - "_inline", - "_multiple_inheritance", - "_pascal", - "_single_inheritance", - "_stdcall", - "_virtual_inheritance", - "_w64", - - "__abstract", - "__alignof", - "__asm", - "__assume", - "__based", - "__box", - "__builtin_alignof", - "__cdecl", - "__clrcall", - "__declspec", - "__delegate", - "__event", - "__except", - "__fastcall", - "__finally", - "__forceinline", - "__gc", - "__hook", - "__identifier", - "__if_exists", - "__if_not_exists", - "__inline", - "__int128", - "__int16", - "__int32", - "__int64", - "__int8", - "__interface", - "__leave", - "__m128", - "__m128d", - "__m128i", - "__m256", - "__m256d", - "__m256i", - "__m64", - "__multiple_inheritance", - "__newslot", - "__nogc", - "__noop", - "__nounwind", - "__novtordisp", - "__pascal", - "__pin", - "__pragma", - "__property", - "__ptr32", - "__ptr64", - "__raise", - "__restrict", - "__resume", - "__sealed", - "__single_inheritance", - "__stdcall", - "__super", - "__thiscall", - "__try", - "__try_cast", - "__typeof", - "__unaligned", - "__unhook", - "__uuidof", - "__value", - "__virtual_inheritance", - "__w64", - "__wchar_t"] - - strings = ["(\\'(.)\\')", "(\"(.)*\")"] - numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] - comments = ["(//(.)*)", "(\/\\*(.*?)\\*\/)"] diff --git a/cupcake/config/languages/language.py b/cupcake/config/languages/language.py deleted file mode 100644 index 70bd280..0000000 --- a/cupcake/config/languages/language.py +++ /dev/null @@ -1,5 +0,0 @@ -class Language: - keywords: list - strings: list - numbers: list - comments: list diff --git a/cupcake/editor/__init__.py b/cupcake/editor/__init__.py index 1b09f1e..2753377 100644 --- a/cupcake/editor/__init__.py +++ b/cupcake/editor/__init__.py @@ -1,81 +1,78 @@ import tkinter as tk -from ..config import Config -from .events import Events -from .linenumbers import LineNumbers +from ...utils import Scrollbar +from ..editor import BaseEditor + from .minimap import Minimap -from .scrollbar import Scrollbar -from .language import SyntaxLoader +from .linenumbers import LineNumbers from .text import Text -from .find_replace import FinderReplacer - - -class Editor(tk.Frame): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - self.config = Config(self) - self.font = self.config.font - self.syntax = SyntaxLoader() - - self.grid_rowconfigure(0, weight=1) - self.grid_columnconfigure(1, weight=1) - - self.text = Text(self) - self.linenumebers = LineNumbers(self, self.text) - self.minimap = Minimap(self, self.text) - self.scrollbar = Scrollbar(self, self.text) - - # actual find-replace widget - # self.find_replace = FindReplace(self, self.text) - # self.find_replace_active = False - self.find_replace = FinderReplacer(self) - self.find_replace.on_close() - #self.text.bind("", self.find_replace.revive) +class TextEditor(BaseEditor): + def __init__(self, master, path=None, exists=True, minimalist=False, *args, **kwargs): + super().__init__(master, path, exists, *args, **kwargs) + self.font = self.base.settings.font + self.minimalist = minimalist + + self.rowconfigure(0, weight=1) + self.columnconfigure(1, weight=1) - self.linenumebers.grid(row=0, column=0, sticky=tk.NS) + self.text = Text(self, path=self.path, exists=self.exists, minimalist=minimalist) + self.linenumbers = LineNumbers(self, text=self.text) + self.scrollbar = Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) + + self.text.config(font=self.font) + self.text.configure(yscrollcommand=self.scrollbar.set) + + if not self.minimalist: + self.minimap = Minimap(self, self.text) + self.minimap.grid(row=0, column=2, sticky=tk.NS) + + self.linenumbers.grid(row=0, column=0, sticky=tk.NS) self.text.grid(row=0, column=1, sticky=tk.NSEW) - self.minimap.grid(row=0, column=2, sticky=tk.NS) self.scrollbar.grid(row=0, column=3, sticky=tk.NS) - self.events = Events(self) - self.text.config(yscrollcommand=self.text_scrolled) - self.focus() + self.text.bind("<>", self.on_change) + self.text.bind("<>", self.on_scroll) + + def on_change(self, *_): + self.text.refresh() + self.linenumbers.redraw() + self.base.update_statusbar() - def text_scrolled(self, *args): - pass + def on_scroll(self, *_): + self.linenumbers.redraw() + if not self.minimalist: + self.minimap.redraw() - def show_find_replace(self, event): - # positioning of the actual find_replace widget - # if not self.find_replace_active: - # pos_x, pos_y, width = self.text.winfo_rootx(), self.text.winfo_rooty(), self.text.winfo_width() - # self.find_replace.show(((pos_x + width) - (self.find_replace.winfo_width() + 10), pos_y)) - # else: - # self.find_replace.reset() - self.find_replace.revive(event) + def unsupported_file(self): + self.text.highlighter.lexer = None + self.text.show_unsupported_dialog() + self.linenumbers.grid_remove() + self.scrollbar.grid_remove() + self.editable = False def focus(self): self.text.focus() - self.refresh_editor() + self.on_change() def set_fontsize(self, size): self.font.configure(size=size) - self.linenumebers.set_bar_width(size * 4) - - def refresh_editor(self, *_): - self.text.on_change() - self.text.highlighter.highlight_all() - self.redraw_ln() - self.minimap.redraw() - self.scrollbar.redraw() - - def redraw_ln(self, *_): - self.linenumebers.redraw() - - def insert(self, text): - self.text.clear_insert(text) + self.linenumbers.set_bar_width(size * 3) + self.on_change() + + def save(self, path=None): + if self.editable: + self.text.save_file(path) - def load_file(self, filepath): - self.text.load_file(filepath) + def cut(self, *_): + if self.editable: + self.text.cut() + + def copy(self, *_): + if self.editable: + self.text.copy() + + def paste(self, *_): + if self.editable: + self.text.paste() diff --git a/cupcake/editor/autocomplete/__init__.py b/cupcake/editor/autocomplete/__init__.py index 7eeba89..65ddd04 100644 --- a/cupcake/editor/autocomplete/__init__.py +++ b/cupcake/editor/autocomplete/__init__.py @@ -1,43 +1,36 @@ import tkinter as tk +from itertools import chain -from .itemkinds import Kinds +from .kinds import Kinds from .item import AutoCompleteItem +from core.components.utils import Toplevel -class AutoComplete(tk.Toplevel): + +class AutoComplete(Toplevel): def __init__(self, master, items=None, active=False, *args, **kwargs): super().__init__(master, *args, **kwargs) - self.master = master - self.autocomplete_kinds = Kinds(self) - self.config(bg="#454545", padx=1, pady=1) + self.config(padx=1, pady=1, bg=self.base.theme.border) self.active = active - self.font = self.master.font - if not self.active: self.withdraw() self.overrideredirect(True) self.wm_attributes("-topmost", True) - self.grid_columnconfigure(0, weight=1) self.menu_items = [] self.active_items = [] - self.row = 0 self.selected = 0 - if items: - self.items = items - # [(completion, type), ...] - + self.items = items # [(completion, type), ...] self.add_all_items() self.refresh_selected() - - self.configure_bindings() + # filter def update_completions(self): self.refresh_geometry() self.update_idletasks() @@ -45,24 +38,28 @@ def update_completions(self): term = self.master.get_current_word() - new = [i for i in self.get_items() if i.get_text() == term] - new += [i for i in self.get_items() if i.get_text().startswith(term)] - new += [i for i in self.get_items() if term in i.get_text() and i.get_kind() != "word" and i not in new] - new += [i for i in self.get_items() if term in i.get_text() and i not in new] + exact, starts, includes = [], [], [] + for i in self.menu_items: + if i.get_text() == term: + exact.append(i) + elif i.get_text().startswith(term): + starts.append(i) + elif term in i.get_text(): + includes.append(i) + new = list(chain(exact, starts, includes)) self.hide_all_items() - if any(new): self.show_items(new[:10] if len(new) > 10 else new, term) else: self.hide() - def move_up(self, *args): + def move_up(self, *_): if self.active: self.select(-1) return "break" - def move_down(self, *args): + def move_down(self, *_): if self.active: self.select(1) return "break" @@ -75,22 +72,16 @@ def add_all_items(self): self.refresh_selected() def update_all_words(self): - for word in self.master.get_all_words(): + for word in self.master.words: if word not in self.get_items_text(): self.add_item(word, "word") - for word in self.get_items(): - if word.get_text() not in self.master.get_all_words() and word.get_kind() == "word": + for word in self.menu_items: + if word.get_text() not in self.master.words and word.get_kind() == "word": self.remove_item(word) - def configure_bindings(self): - # root.bind("" , self.hide) - # root.bind("", self.refresh_geometry) - # root.bind("", self.hide) - ... - - def add_item(self, left, kind=None): - new_item = AutoCompleteItem(self, left, kind=kind) + def add_item(self, text, kind=None): + new_item = AutoCompleteItem(self, text, kind=kind) new_item.grid(row=self.row, sticky=tk.EW) self.menu_items.append(new_item) @@ -120,9 +111,6 @@ def refresh_selected(self): i.deselect() if self.selected < len(self.active_items): self.active_items[self.selected].select() - - def get_items(self): - return self.menu_items def get_items_text(self): return [i.get_text() for i in self.menu_items] @@ -144,7 +132,7 @@ def show_items(self, items, term): self.reset_selection() - def refresh_geometry(self, *args): + def refresh_geometry(self, *_): self.update_idletasks() self.geometry("+{}+{}".format(*self.master.cursor_screen_location())) @@ -154,7 +142,7 @@ def show(self, pos): self.geometry("+{}+{}".format(*pos)) self.deiconify() - def hide(self, *args): + def hide(self, *_): self.active = False self.withdraw() self.reset() @@ -162,10 +150,10 @@ def hide(self, *args): def reset(self): self.reset_selection() - def choose(self, this=None, *args): - self.hide() + def choose(self, this=None, *_): if not this: this = self.active_items[self.selected] self.master.confirm_autocomplete(this.get_text()) + self.hide() return "break" diff --git a/cupcake/editor/autocomplete/item.py b/cupcake/editor/autocomplete/item.py index e361049..069f994 100644 --- a/cupcake/editor/autocomplete/item.py +++ b/cupcake/editor/autocomplete/item.py @@ -1,67 +1,28 @@ -from cgitb import text import tkinter as tk -from .itemkinds import Kinds +from .kind import Kind +from core.components.utils import Frame - -class Kind(tk.Label): - def __init__(self, master, kinds, kind="text", *args, **kwargs): +class AutoCompleteItem(Frame): + def __init__(self, master, text, kind=None, *args, **kwargs): super().__init__(master, *args, **kwargs) - self.master = master - self.kinds = kinds - self.kind = kind - - self.image = None + self.config(width=400, **self.base.theme.editors.autocomplete) + self.bg, self.fg, self.hbg, self.hfg = self.base.theme.editors.autocomplete.item.values() - self.config_appearance() - self.config_image() - - def config_appearance(self): - self.config(bg="#252526") - - def config_image(self): - match self.kind: - case "method": - self.image = self.kinds.imethods - case "variable": - self.image = self.kinds.ivariables - case "field": - self.image = self.kinds.ifields - case "class": - self.image = self.kinds.iclasses - case "interface": - self.image = self.kinds.iinterfaces - case "module": - self.image = self.kinds.imodules - case "property": - self.image = self.kinds.iproperties - case "keyword": - self.image = self.kinds.ikeywords - case _: - self.image = self.kinds.iwords - self.config(image=self.image) - -class AutoCompleteItem(tk.Frame): - def __init__(self, master, left, kind=None, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.left = left + self.text = text self.kind = kind self.kindw = Kind(self, self.master.autocomplete_kinds, kind) - self.leftw = tk.Text(self, - font=(self.master.font['family'], 10), fg="#d4d4d4", - bg="#252526", relief=tk.FLAT, highlightthickness=0, width=30, height=1) - self.leftw.insert(tk.END, left) - self.leftw.config(state=tk.DISABLED) + self.textw = tk.Text(self, + font=self.base.settings.font, fg=self.fg, bg=self.bg, + relief=tk.FLAT, highlightthickness=0, width=30, height=1) + self.textw.insert(tk.END, text) + self.textw.config(state=tk.DISABLED) - self.leftw.tag_config("term", foreground="#18a3ff") + self.textw.tag_config("term", foreground=self.base.theme.biscuit) - self.config(bg="#1e1e1e", width=300) - self.kindw.bind("", self.on_click) - self.leftw.bind("", self.on_click) + self.textw.bind("", self.on_click) self.bind("", self.on_hover) self.bind("", self.off_hover) @@ -73,33 +34,33 @@ def __init__(self, master, left, kind=None, *args, **kwargs): self.grid_rowconfigure(0, weight=1) self.kindw.grid(row=0, column=0, sticky=tk.NSEW) - self.leftw.grid(row=0, column=1, sticky=tk.NSEW) + self.textw.grid(row=0, column=1, sticky=tk.NSEW) def get_text(self): - return self.left + return self.text def get_kind(self): return self.kind def mark_term(self, term): - start_pos = self.left.find(term) + start_pos = self.text.find(term) end_pos = start_pos + len(term) - self.leftw.tag_remove("term", 1.0, tk.END) - self.leftw.tag_add("term", f"1.{start_pos}", f"1.{end_pos}") + self.textw.tag_remove("term", 1.0, tk.END) + self.textw.tag_add("term", f"1.{start_pos}", f"1.{end_pos}") def on_click(self, *args): self.master.choose(self) def on_hover(self, *args): if not self.selected: - self.kindw.config(bg="#2a2d2e") - self.leftw.config(bg="#2a2d2e") + self.kindw.config(bg=self.hbg) + self.textw.config(bg=self.hbg) self.hovered = True def off_hover(self, *args): if not self.selected: - self.kindw.config(bg="#252526") - self.leftw.config(bg="#252526") + self.kindw.config(bg=self.bg) + self.textw.config(bg=self.bg) self.hovered = False def toggle_selection(self): @@ -109,11 +70,11 @@ def toggle_selection(self): self.deselect() def select(self): - self.kindw.config(bg="#094771") - self.leftw.config(bg="#094771", fg="#ffffff") + self.kindw.config(bg=self.hbg) + self.textw.config(bg=self.hbg, fg=self.hfg) self.selected = True def deselect(self): - self.kindw.config(bg="#252526") - self.leftw.config(bg="#252526", fg="#d4d4d4") + self.kindw.config(bg=self.bg) + self.textw.config(bg=self.bg, fg=self.fg) self.selected = False \ No newline at end of file diff --git a/cupcake/editor/autocomplete/kind.py b/cupcake/editor/autocomplete/kind.py new file mode 100644 index 0000000..0fe8320 --- /dev/null +++ b/cupcake/editor/autocomplete/kind.py @@ -0,0 +1,41 @@ +import tkinter as tk + + +class Kind(tk.Label): + def __init__(self, master, kinds, kind="text", *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base + + self.kinds = kinds + self.kind = kind + + self.image = None + + self.config_appearance() + self.config_image() + + def config_appearance(self): + self.config(**self.base.theme.editors.autocomplete) + + def config_image(self): + match self.kind: + case "method": + self.image = self.kinds.imethods + case "variable": + self.image = self.kinds.ivariables + case "field": + self.image = self.kinds.ifields + case "class": + self.image = self.kinds.iclasses + case "interface": + self.image = self.kinds.iinterfaces + case "module": + self.image = self.kinds.imodules + case "property": + self.image = self.kinds.iproperties + case "keyword": + self.image = self.kinds.ikeywords + case _: + self.image = self.kinds.iwords + self.config(image=self.image) \ No newline at end of file diff --git a/cupcake/editor/autocomplete/itemkinds.py b/cupcake/editor/autocomplete/kinds.py similarity index 70% rename from cupcake/editor/autocomplete/itemkinds.py rename to cupcake/editor/autocomplete/kinds.py index 2504849..f4a0b43 100644 --- a/cupcake/editor/autocomplete/itemkinds.py +++ b/cupcake/editor/autocomplete/kinds.py @@ -52,26 +52,26 @@ def __init__(self, master, *args, **kwargs): 2WtJ9TiOf6yZfDiEMDaz7SiKPhiApM9A2d0bGyY3gPJ8Pv8CS1/l7mEDXsgNYDwefwP2KpXKmXW4yPcajcbXvzh Jkn1JO+7+JMuyq6tgr9e75u5PJe00m82fC7Mnk8lt4FUI4fEqLOkR8LLoAfALta2R2TiY6xMAAAAASUVORK5CYI I=""") - self.iproperties = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAACXB - IWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAdFJREFUKJGN0r - 9rFEEUB/Dvd+8uxzUhoBCCEkhtEbTbHVdZIiQQfySKWmkjWtkIWtj4D1imsDQIRlCDoBbauOLNzR5aGrBVEY0g2 - gQCtzfztTALinenrxt4n++8Nwwxorrd7ni/379O8gyAKQDvAdwyxqwAQDQCTnrvLcndJBcBnAewC0Be9QzF3vub - JJ8aYy6GEGYkrZBcMMZsWGv3WWsvcxDM83xibGzsQ6/X29NoNPaTfExyPkmS1+12ezaKoucAfgy8udVqTZP8lGX - ZVlmW7yR9BLBYFMUBks9IXgEwM2zsr5Km8jyvZ1n2rSzLuRDCUgjhhaRLkt4C2ByIvfezANBsNicBoAqQtJym6R - NJ1wCs/7VzURRHQgh3oyg6Gcdxx1o7XZbl5yzL+t1ud9x7fwPA0RBC/Ad2zs1JWvsNZiQfAQgANgHsBfCQ5NUkS - b7XK2itzSStSToVx3GnCgJwnOSG936iVqt9SZJkuzLcgQdJrocQzqZp+tI5d1jS/eo85FHBTqdzCMADAKeNMa+c - c/OS7lSjD4MAUAdwG8C5HbggaRXAUhzHxSgI/PqeLUnbzrllSashhBPGmH9CAKhLukDynqQtAMfSNH3zPxAAfgL - 2/u9cQzl88QAAAABJRU5ErkJggg==""") - self.ikeywords = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAACXBIW - XMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAPRJREFUKJGlkrFK - xEAURc8bHltbWqytYG0TYiC2VnZ+hK3iByhouX7HfkcmhAgigpXFgt02gpXETObZiEVMWI23PecOl+HBPyLe+5c - xaGaLLMsWY1yBHRE5NLO3Hrtwzm0BFEVxJCI3Q2XM7BIIPbYLPAOo6n2M8exHWUROxmaFEJ4AkiRZA+sxb1KkLM - vrTVKM8SHLsmVVVfMY4ynwnqbplZrZbFPZOacATdOIqs7MrP8/E2Z774+HQAjhMc/zFUBVVfOu6/b7jgLnQ2VVv - QVWXw/ticigNzlS1/X2b+UQwkeapq/f69q2Hb3tgdwBB39ZN5pP4uJac+7GJRAAAAAASUVORK5CYII=""") - self.iwords = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAA8AAAAICAYAAAAm06XyAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA - GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAATBJREFUGJWFjyFLQ2EYhc95t31JEQYiGExaLBO - DYbvJaFBEGBqHGNW4n6BFBUGw2ZUJCoaxMGT3Xm+1LwkLgrILwzLZdwy7wTD0iec9z4EXmEAcx40wDPcn3X5j/x - X+gmEYLgA4JLkE4KFSqdzEcdwA0Jc0A0CSzoMgSNrt9pRzrg5gRVLXSNZIfki6AnDa6XRKGBsbku5IPpNsJkky5 - 5x7AjAv6ZLkV344HJ4VCoUdM9uUNMrlcsuZfBEEwS0ARFG0670/BrDY6/XWq9XqCEAr75xrABgAeASwKilHElmG - bIgkpwF8ZuL45yiKUgCBpG+SLwCOSG5LmvXebwEomdk9yTVJife+ViwWm/1+f9tI1iW1ssIrgIGkd0lmZm9mdk1 - yr1wudwFUzewkTdOU5MEPhImOHsSTJnYAAAAASUVORK5CYII=""") + self.iproperties = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAA + AACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAdFJREFUK + JGN0r9rFEEUB/Dvd+8uxzUhoBCCEkhtEbTbHVdZIiQQfySKWmkjWtkIWtj4D1imsDQIRlCDoBbauOLNzR5aGrBV + EY0g2gQCtzfztTALinenrxt4n++8Nwwxorrd7ni/379O8gyAKQDvAdwyxqwAQDQCTnrvLcndJBcBnAewC0Be9Qz + F3vubJJ8aYy6GEGYkrZBcMMZsWGv3WWsvcxDM83xibGzsQ6/X29NoNPaTfExyPkmS1+12ezaKoucAfgy8udVqTZ + P8lGXZVlmW7yR9BLBYFMUBks9IXgEwM2zsr5Km8jyvZ1n2rSzLuRDCUgjhhaRLkt4C2ByIvfezANBsNicBoAqQt + Jym6RNJ1wCs/7VzURRHQgh3oyg6Gcdxx1o7XZbl5yzL+t1ud9x7fwPA0RBC/Ad2zs1JWvsNZiQfAQgANgHsBfCQ + 5NUkSb7XK2itzSStSToVx3GnCgJwnOSG936iVqt9SZJkuzLcgQdJrocQzqZp+tI5d1jS/eo85FHBTqdzCMADAKe + NMa+cc/OS7lSjD4MAUAdwG8C5HbggaRXAUhzHxSgI/PqeLUnbzrllSashhBPGmH9CAKhLukDynqQtAMfSNH3zPx + AAfgL2/u9cQzl88QAAAABJRU5ErkJggg==""") + self.ikeywords = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAA + CXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAPRJREFUKJG + lkrFKxEAURc8bHltbWqytYG0TYiC2VnZ+hK3iByhouX7HfkcmhAgigpXFgt02gpXETObZiEVMWI23PecOl+HBPy + Le+5cxaGaLLMsWY1yBHRE5NLO3Hrtwzm0BFEVxJCI3Q2XM7BIIPbYLPAOo6n2M8exHWUROxmaFEJ4AkiRZA+sxb + 1KkLMvrTVKM8SHLsmVVVfMY4ynwnqbplZrZbFPZOacATdOIqs7MrP8/E2Z774+HQAjhMc/zFUBVVfOu6/b7jgLn + Q2VVvQVWXw/ticigNzlS1/X2b+UQwkeapq/f69q2Hb3tgdwBB39ZN5pP4uJac+7GJRAAAAAASUVORK5CYII=""") + self.iwords = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAA8AAAAICAYAAAAm06XyAAAACXB + IWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAATBJREFUGJWFjy + FLQ2EYhc95t31JEQYiGExaLBODYbvJaFBEGBqHGNW4n6BFBUGw2ZUJCoaxMGT3Xm+1LwkLgrILwzLZdwy7wTD0i + ec9z4EXmEAcx40wDPcn3X5j/xX+gmEYLgA4JLkE4KFSqdzEcdwA0Jc0A0CSzoMgSNrt9pRzrg5gRVLXSNZIfki6 + AnDa6XRKGBsbku5IPpNsJkky55x7AjAv6ZLkV344HJ4VCoUdM9uUNMrlcsuZfBEEwS0ARFG0670/BrDY6/XWq9X + qCEAr75xrABgAeASwKilHElmGbIgkpwF8ZuL45yiKUgCBpG+SLwCOSG5LmvXebwEomdk9yTVJife+ViwWm/1+f9 + tI1iW1ssIrgIGkd0lmZm9mdk1yr1wudwFUzewkTdOU5MEPhImOHsSTJnYAAAAASUVORK5CYII=""") diff --git a/cupcake/editor/events.py b/cupcake/editor/events.py deleted file mode 100644 index 626d77c..0000000 --- a/cupcake/editor/events.py +++ /dev/null @@ -1,13 +0,0 @@ -class Events: - def __init__(self, master, *args, **kwargs): - self.master = master - - self.bind_all() - - def bind(self, key, fn): - self.master.text.bind(key, fn) - - def bind_all(self): - self.bind("<>", self.master.refresh_editor) - self.bind("", self.master.redraw_ln) - self.bind("", self.master.show_find_replace) diff --git a/cupcake/editor/find_replace/__init__.py b/cupcake/editor/find_replace/__init__.py deleted file mode 100644 index 0af3ec4..0000000 --- a/cupcake/editor/find_replace/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -import tkinter as tk - -from .entrybox import EntryBox -from .button import Button -from .findbox import FindBox -from .replacebox import ReplaceBox -from .results import FindResults -from .togglew import ToggleWidget -from .container import FindReplaceContainer - -from .find_replace import FinderReplacer - - -class FindReplace(tk.Toplevel): - def __init__(self, master, tw, state=False, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - self.tw = tw - - self.state = state - self.font = self.master.font - - if not state: - self.withdraw() - - self.overrideredirect(True) - self.config(bg="#454545") - - self.togglew = ToggleWidget(self) - self.container = FindReplaceContainer(self) - - self.rowconfigure(0, weight=1) - self.grid_columnconfigure(1, weight=1) - - self.togglew.grid(row=0, column=0, sticky=tk.NS, padx=(2, 0)) - self.container.grid(row=0, column=1, sticky=tk.NSEW) - - self.config_bindings() - - def config_bindings(self, *args): - self.container.find_entry.entry.bind("", self.do_find) - - def toggle_replace(self, state): - self.container.toggle_replace(state) - - def do_find(self, *args): - print(self.container.get_term()) - # self.master.highlighter.highlight_pattern(self.container.find_entry.entry.get()) - - def refresh_geometry(self, *args): - self.update_idletasks() - self.geometry("+{}+{}".format(*self.master.cursor_screen_location())) - - def show(self, pos): - self.state = True - self.update_idletasks() - self.geometry("+{}+{}".format(*pos)) - self.deiconify() - self.master.find_replace_active = True - - def hide(self, *args): - self.state = False - self.withdraw() - self.master.find_replace_active = False - - def reset(self, *args): - ... - diff --git a/cupcake/editor/find_replace/button.py b/cupcake/editor/find_replace/button.py deleted file mode 100644 index a61ed19..0000000 --- a/cupcake/editor/find_replace/button.py +++ /dev/null @@ -1,41 +0,0 @@ -import tkinter as tk - - -class Button(tk.Frame): - def __init__(self, master, bg, hbg, img=None, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - self.img = img - self.hovered = False - - self.hbg = hbg - self.bg = bg - - self.imagew = tk.Label(self, image=self.img) - self.imagew.config(bg=self.bg, relief=tk.FLAT) - self.imagew.grid(row=0, column=0, sticky=tk.NS) - - self.config(bg=self.bg, pady=3, padx=3, cursor="hand2") - self.config_bindings() - - def config_bindings(self): - self.bind("", self.on_hover) - self.bind("", self.off_hover) - self.bind("", self.on_click) - - def set_image(self, img): - self.img = img - self.imagew.config(image=self.img) - - def on_click(self, *args): - pass - - def on_hover(self, *args): - self.hovered = True - self.imagew.config(bg=self.hbg) - self.config(bg=self.hbg) - - def off_hover(self, *args): - self.hovered = False - self.imagew.config(bg=self.bg) - self.config(bg=self.bg) diff --git a/cupcake/editor/find_replace/container.py b/cupcake/editor/find_replace/container.py deleted file mode 100644 index 468e769..0000000 --- a/cupcake/editor/find_replace/container.py +++ /dev/null @@ -1,68 +0,0 @@ -import tkinter as tk - -from . import Button -from . import FindResults -from . import FindBox -from . import ReplaceBox - - -class FindReplaceContainer(tk.Frame): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.config(bg="#252526") - self.replace_enabled = False - - self.find_entry = FindBox(self, width=20) - self.replace_entry = ReplaceBox(self, width=20) - - self.find_results = FindResults(self) - - self.replace_btn_holder = tk.Frame(self, bg="#252526", pady=2) - self.replace_button = Button(self.replace_btn_holder, bg="#252526", hbg="#4b4c4d", img=tk.PhotoImage(data=""" - iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzEAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2Nhc - GUub3Jnm+48GgAAAjBJREFUOI2VUj1olEEQfbO7lwODEAjeCSoSIaAcWJgIosct+4mCYGORNoUWIqTRQsTKLqBYp1Dwp/xEJAFDwMC3+yE2Br - SxEUEsEiWJEDgTyG2+GQsvkjsTo6/bZd6892YG+A947xe7/8zfCCGE0wDOFUXxPkmSSQA9IYQrAKK19ikA0GZxnuf7iqIYNcYsMXNRFMU3pVQ - PgBUAz2OMx0ql0gKAMQA3ROSac+71bwfMPEVEg8y8CGBNa/2WmfcqpZoiYsrl8h5mjtbaJ977swD2d0c4Yoy5XK/Xp7ZkzkXkE4BYFIUQ0VoI - YZaIDiulrgOA8d4/IqIZEWnFGAe8969E5JJz7oeIJK1Wq9zf398aHh6O09PTA319fWp9fT02Go0NACDv/QUAzwAsAKiKyAQzP9Ba39xtK0T0k - gAgy7KLSqlJAPestbeyLDsK4OA/NHhIaZrqarVqY4yHtNbzu5FE5GuSJB/aM/piKpXKYxHRxpjmbuS2aiOEcNVamwO/tnCeiE5Zaz8DQJqmul - ar6Vqt1morKu+9cs5ttFVTZj4OIAcAtbV7CGGwUqm8WF5efhNCGM2yrB5CeKeUmk3TVG/nqOOUe3t755vNZg7AATgDoEFE9zfPdjt0OFhdXR0 - hohMAAgAopZYAnPTej8zNzZW24ceOBsw8KSIfieg7M0+Vy+VxEVkQkQNDQ0Mb3WwRGeuI4JxbAXCnq258J/vOuRlDRHdFxHvv/1DYARHA7c3H - T51aBFm+qi3JAAAAAElFTkSuQmCC""")) - - self.find_btns_holder = tk.Frame(self, bg="#252526", pady=6) - self.selection_button = Button(self.find_btns_holder, bg="#252526", hbg="#4b4c4d", img=tk.PhotoImage(data=""" - iVBORw0KGgoAAAANSUhEUgAAABEAAAALCAYAAACZIGYHAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2Nhc - GUub3Jnm+48GgAAAGdJREFUKJFjPHnx5n8GEsF/RgZvCz31bTA+yz8GJmNSDWH8xnCHVD30AYynLt+RpdQQlv///j6i2BAGhv9KlBoyeADjkX - M3pIhRyPGP8ZeJifobbHIsrMyMT4kx5A8zwykGBgZzbHIAsMwZm/JJgOgAAAAASUVORK5CYII=""")) - self.close_button = Button(self.find_btns_holder, bg="#252526", hbg="#4b4c4d", img=tk.PhotoImage(data=""" - iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2Nhc - GUub3Jnm+48GgAAALhJREFUGJV1kL0OAVEQhb9ze61HkK2sxk8k3sqqUNhEgafS2Evjqna9hFKiMAok3OxOOfOdM3NGPlwz8WwP0iSjoYpzmT - t4OBMtQ9NjKLdNoKS5ida7cbmufKgsFhTnMvehMh/Kzb9DJKgDFQtktjB7FpIbgW2HaTKrhQF8qPbABLPTsJcMfmcuvhGYgB2Q+nEG9wtKmn9 - Wj03Ka7/UlDoOrchxRlTf0MJ2TtIdWNeBAKNuZwmsDd1eZYKFNbEaSb4AAAAASUVORK5CYII=""")) - - self.grid_columnconfigure(0, weight=1) - - self.find_entry.grid(row=0, column=0, sticky=tk.NSEW, pady=5) - - self.find_results.grid(row=0, column=1, sticky=tk.NSEW, pady=5) - self.replace_button.grid(row=0, column=0, sticky=tk.NS, padx=5) - - self.find_btns_holder.grid(row=0, column=2, sticky=tk.NSEW, padx=(10, 5)) - - self.selection_button.grid(row=0, column=0, sticky=tk.NSEW, pady=3) - self.close_button.grid(row=0, column=1, sticky=tk.NSEW, pady=3) - - def get_term(self): - return self.find_entry.get() - - def toggle_replace(self, state): - if self.replace_enabled: - self.replace_enabled = False - self.replace_entry.grid_remove() - self.replace_btn_holder.grid_remove() - else: - self.replace_enabled = True - self.replace_entry.grid(row=1, column=0, sticky=tk.NSEW, pady=(0, 5)) - self.replace_btn_holder.grid(row=1, column=1, sticky=tk.NSEW, pady=(0, 5)) diff --git a/cupcake/editor/find_replace/entrybox.py b/cupcake/editor/find_replace/entrybox.py deleted file mode 100644 index e08ecc8..0000000 --- a/cupcake/editor/find_replace/entrybox.py +++ /dev/null @@ -1,39 +0,0 @@ -import tkinter as tk -from tkinter import font - - -class EntryBox(tk.Frame): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.config(bg="#3c3c3c", padx=1, pady=1) - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - - self.term = tk.StringVar() - - self.entry_frame = frame = tk.Frame(self, bg="#3c3c3c") - frame.grid(row=0, column=0, sticky=tk.NSEW) - - self.entry = tk.Entry( - frame, width=30, bg="#3c3c3c", fg="#ccccc7", font=("Helvetica", 11), - textvariable=self.term, relief=tk.FLAT, insertbackground="#aeafad") - self.entry.grid(sticky=tk.EW, padx=3, pady=3) - - self.config_bindings() - - def get(self): - return self.term.get() - - def config_bindings(self, *args): ... - # self.entry.bind("", self.on_focus) - # self.entry.bind("", self.off_focus) - - def on_focus(self, *args): - self.update_idletasks() - self.config(bg="#007fd4") - - def off_focus(self, *args): - self.update_idletasks() - self.config(bg="#3c3c3c") \ No newline at end of file diff --git a/cupcake/editor/find_replace/find_replace.py b/cupcake/editor/find_replace/find_replace.py deleted file mode 100644 index b3901ec..0000000 --- a/cupcake/editor/find_replace/find_replace.py +++ /dev/null @@ -1,226 +0,0 @@ -import tkinter as tk -import re - - -class FinderReplacer: - """A class to hold all the find/replace functionality - it will have the following attributes: - - matchstring - the string that the user wants to find. - replacestring - the string that the user wants to replace, - if the user just wants to search is None. - matches - a dict of matches with position as key and match - object as value. - current - the current position the user is interacting with - """ - - def __init__(self, parent, matchstring=None, replacestring=None): - self.matchstring = matchstring - self.replacestring = replacestring - self.matches = None - self.parent = parent - self.parent.text.tag_configure("found", background="green") - self.parent.text.tag_configure( - "foundcurrent", background="orange") - self.display() - - @property - def text(self): - return self.parent.text.get(1.0, tk.END) - - @property - def current(self): - if not self.parent.text.count("1.0", self.parent.text.index(tk.INSERT), "chars"): - return 0 - else: - return self.parent.text.count("1.0", self.parent.text.index(tk.INSERT), "chars")[0] - - def display(self): - self.window = tk.Toplevel(self.parent) - self.window.geometry("500x300") - self.window.title("find & replace") - self.window_ROWS = 5 - self.window_COLS = 3 - - for i in range(self.window_ROWS): - self.window.rowconfigure(i, minsize=35) - for i in range(self.window_COLS): - self.window.columnconfigure(i, minsize=30) - self.find_tag = tk.Label(self.window, text="Find: ") - self.find_entry = tk.Entry(self.window) - self.replace_tag = tk.Label(self.window, text="Replace for:") - self.replace_entry = tk.Entry(self.window) - self.find_tag.grid(row=1, column=1) - self.find_entry.grid(row=1, column=2) - self.replace_tag.grid(row=2, column=1) - self.replace_entry.grid(row=2, column=2) - self.find_button = tk.Button( - self.window, text="Highlight", command=self.find) - self.find_button.grid(row=1, column=4) - self.next_button = tk.Button( - self.window, text="->", command=self.next_match) - self.next_button.grid(row=1, column=5) - self.prev_button = tk.Button( - self.window, text="<-", command=self.prev_match) - self.prev_button.grid(row=1, column=3) - self.replace_button = tk.Button( - self.window, text="Change it!", command=self.replace) - self.replace_all_button = tk.Button( - self.window, text="ALL", command=self.replace_all) - self.replace_button.grid(row=2, column=4) - self.replace_all_button.grid(row=2, column=5) - self.window.protocol("WM_DELETE_WINDOW", self.on_close) - - def highlight_matches(self): - self.parent.text.tag_remove("found", "1.0", "end") - self.parent.text.tag_remove("foundcurrent", "1.0", "end") - for pos, match in self.matches.items(): - start = match.start() - end = match.end() - self.parent.text.tag_add( - "found", f"1.0+{start}c", f"1.0+{end}c") - if self.is_on_match(): - self.highlight_current() - - def highlight_current(self): - self.parent.text.tag_remove("foundcurrent", "1.0", "end") - current = self.current - match = self.matches[current] - start = match.start() - end = match.end() - self.parent.text.tag_add( - "foundcurrent", f"1.0+{start}c", f"1.0+{end}c") - - def get_find_input(self): - if self.find_entry.get() == "": - self.parent.text.tag_remove("found", "1.0", "end") - self.parent.text.tag_remove("foundcurrent", "1.0", "end") - return - current = self.current - self.matches = {} - self.matchstring = self.find_entry.get() - self.re_ = re.compile(self.matchstring) - for match in self.re_.finditer(self.text): - self.matches[match.start()] = match - self.highlight_matches() - self.parent.text.mark_set("insert", f"1.0 + {current}c") - - def find(self): - self.get_find_input() - self.parent.lift() - self.parent.text.focus() - - def next_match(self): - """Moves the editor focus to the next match""" - if self.find_entry.get() != self.matchstring: - self.get_find_input() - matchpos = [i for i in sorted(self.matches.keys()) if i > self.current] - if len(matchpos) > 0: - next_index = f"1.0 + {matchpos[0]}c" - self.parent.text.mark_set("insert", next_index) - self.parent.text.see(next_index) - self.highlight_current() - elif len(self.matches) > 0: - self.parent.text.mark_set("insert", "1.0") - if self.is_on_match(): - self.highlight_current() - else: - self.next_match() - self.parent.lift() - self.parent.text.focus() - - def prev_match(self): - """Moves the editor focus to the previous match""" - if self.find_entry.get() != self.matchstring: - self.get_find_input() - matchpos = [i for i in sorted(self.matches.keys()) if i < self.current] - if len(matchpos) > 0: - next_index = f"1.0 + {matchpos[-1]}c" - self.parent.text.mark_set("insert", next_index) - self.parent.text.see(next_index) - self.highlight_current() - elif len(self.matches) > 0: - self.parent.text.mark_set("insert", "end") - self.prev_match() - self.parent.lift() - self.parent.text.focus() - - def replace(self): - """replaces current (in focus) match, removing the match and writing the replace string""" - self.replacestring = self.replace_entry.get() - if self.find_entry.get() != self.matchstring: - self.get_find_input() - if self.is_on_match(): - match = self.matches[self.current] - self.parent.text.delete( - f"1.0 + {match.start()}c", f"1.0 + {match.end()}c") - self.parent.text.insert( - f"1.0 + {self.current}c", self.replacestring) - self.get_find_input() - self.parent.lift() - self.parent.text.focus() - - def is_on_match(self): - """tells if the editor is currently pointing to a match""" - if self.current in self.matches.keys(): - return True - else: - return False - - def on_close(self): - """removes the highlighting of the find string when the window is closed""" - self.parent.text.tag_remove("found", "1.0", "end") - self.parent.text.tag_remove("foundcurrent", "1.0", "end") - self.window.withdraw() - - def replace_all(self): - """replaces all occurences of the string for the replace string, it will even replace partial words.""" - self.get_find_input() - nmatches = len(self.matches) - current = self.current - self.parent.text.mark_set("insert", "1.0") - self.replace() - for i in range(nmatches): - self.next_match() - self.replace() - self.parent.text.mark_set("insert", f"1.0 + {current}c") - - def revive(self, event): - """brings the window back""" - if self.parent.text.tag_ranges(tk.SEL): - selection = self.parent.text.get(tk.SEL_FIRST, tk.SEL_LAST) - self.find_entry.delete("0", "end") - self.find_entry.insert("0", selection) - self.parent.text.mark_set("insert", tk.SEL_FIRST) - self.get_find_input() - - self.window.deiconify() - self.window.lift() - self.find_entry.focus() - - -if __name__ == '__main__': - - class EditorMock(tk.Tk): - def __init__(self, text=""): - super().__init__() - self.text = tk.Text(self) - self.text.textw = self.text - self.text.pack() - self.text.insert(tk.END, text) - self.findr = FinderReplacer(self) - self.bind("", self.findr.revive) - self.mainloop() - - - - e = EditorMock(text="""EMACS: The Extensible, Customizable Display Editor -You are reading about GNU Emacs, the GNU incarnation of the advanced, self-documenting, customizable, extensible editor Emacs. (The ‘G’ in GNU (GNU’s Not Unix) is not silent.) - -We call Emacs advanced because it can do much more than simple insertion and deletion of text. It can control subprocesses, indent programs automatically, show multiple files at once, edit remote files like they were local files, and more. Emacs editing commands operate in terms of characters, words, lines, sentences, paragraphs, and pages, as well as expressions and comments in various programming languages. - -Self-documenting means that at any time you can use special commands, known as help commands, to find out what your options are, or to find out what any command does, or to find all the commands that pertain to a given topic. See Help. - -Customizable means that you can easily alter the behavior of Emacs commands in simple ways. For instance, if you use a programming language in which comments start with ‘<**’ and end with ‘**>’, you can tell the Emacs comment manipulation commands to use those strings (see Manipulating Comments). To take another example, you can rebind the basic cursor motion commands (up, down, left and right) to any keys on the keyboard that you find comfortable. See Customization. - -Extensible means that you can go beyond simple customization and create entirely new commands. New commands are simply programs written in the Lisp language, which are run by Emacs’s own Lisp interpreter. Existing commands can even be redefined in the middle of an editing session, without having to restart Emacs. Most of the editing commands in Emacs are written in Lisp; the few exceptions could have been written in Lisp but use C instead for efficiency. Writing an extension is programming, but non-programmers can use it afterwards. See Preface in An Introduction to Programming in Emacs Lisp, if you want to learn Emacs Lisp programming.""") diff --git a/cupcake/editor/find_replace/findbox.py b/cupcake/editor/find_replace/findbox.py deleted file mode 100644 index 822a720..0000000 --- a/cupcake/editor/find_replace/findbox.py +++ /dev/null @@ -1,34 +0,0 @@ -import tkinter as tk - -from . import Button -from . import EntryBox - - -class FindBox(EntryBox): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.btn_frame = tk.Frame(self, bg="#3c3c3c") - self.btn_frame.grid(row=0, column=1, sticky=tk.NSEW) - - self.full_word = Button(self.btn_frame, bg="#3c3c3c", hbg="#4b4c4d", img=tk.PhotoImage(data=""" - iVBORw0KGgoAAAANSUhEUgAAABMAAAANCAYAAABLjFUnAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHR - Tb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAahJREFUKJGdkrFrU1EYxX/fS4JGdKkOLo5NI9jYak - wqpA7OxaHi5uDSQXASOgniHyAdXERBKNV/wKEuLiKIvc8MuemS9wSpUARByJBBjH33uLQhtiQGf+s999xzv - vvBhDifrDufPBqniSY1m4T8odcXDV2QrEjEl1y/t1mtVn8Paz61Ps9nll0zs6+12dJrM9OoZE8EDTM7h3iY - FU6+lWRD53eCacOghngWb6ePhy8bI2g2v53ICr1uCGHm6vz5HeeTdWCvVimtmJncdqdKsA99+3V2sVLpHq3 - ZSpdkum3icma9KaBAlJ8CdvYluwe16rPlpvPJz+Ph2DQQ/1XT+c51TK8Qm8oVGvWLM6eB7qjk+8WCxGBmg2 - TC6obeL8yVXwJIiuJ2OvK3m+2knEnFflGdo2YhfLQoeuB8uoppL24nt8BOHfK44XyyC5CJ+8Bao1zuDcy2f - PI8p+jplbnpd86nK0a4KfgRyN0zhSURvgOY9AazWNgl4AxorVYpvYCDdQl3cT5R3EqXx89mPHErXXY+UR5A - xsKWT0euyb8Q1AHyBhJa/W+nIf4AQku6KGfDUgAAAAAASUVORK5CYII=""")) - self.regex_button = Button(self.btn_frame, bg="#3c3c3c", hbg="#4b4c4d", img=tk.PhotoImage(data=""" - iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0 - d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAPJJREFUKJGtkL1KwwAUhb+bFK0SCp18g4o4VBGsi4+ig7joC/ga - Qp9AN/sSDg4m6WB+DDYNiJu4KS5qaY6DiJXETp7tXr5zDhyYo2FUbAZx/jz7a9SBfjQ6WLL3iw/N/NL7FTTZciqw - X7TM7OiN5q1K7QKEcX5s5WSE2Le6BklOkBaHVnKKoybwhDjpdVfPKw0AUfTQQuW6mVxAQstC7cFAbqXhKknaC1q8 - AzLX3P6U6Vkp7TnQB0tq1/HjYgcgvBlvBEn+AnCZZd4wyrdrDd+6TsdrYZz/SrUgzl9nzsdet9OZF9IAvB9e3t/o - l2pX+lfDJ85DYGsTUOfAAAAAAElFTkSuQmCC""")) - - self.full_word.grid(row=0, column=0, sticky=tk.NSEW, pady=2, padx=(3, 1)) - self.regex_button.grid(row=0, column=2, sticky=tk.NSEW, pady=2, padx=(1, 3)) diff --git a/cupcake/editor/find_replace/replacebox.py b/cupcake/editor/find_replace/replacebox.py deleted file mode 100644 index f88565c..0000000 --- a/cupcake/editor/find_replace/replacebox.py +++ /dev/null @@ -1,25 +0,0 @@ -import tkinter as tk - -from . import Button -from . import EntryBox - - -class ReplaceBox(EntryBox): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.btn_frame = tk.Frame(self, bg="#3c3c3c") - self.btn_frame.grid(row=0, column=1, sticky=tk.NSEW) - - self.keep_case = Button(self.btn_frame, bg="#3c3c3c", hbg="#4b4c4d", img=tk.PhotoImage(data=""" - iVBORw0KGgoAAAANSUhEUgAAABAAAAAKCAYAAAC9vt6cAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB - 3d3cuaW5rc2NhcGUub3Jnm+48GgAAAYVJREFUKJFtkD1rVGEQhZ957xsEQ2ysRTBcNteQzUbQLIKFIJgfkGXzBwTBRv+Ajb - WgAW1E7MRixSIgaIpYuJrrLmY/JO4HWNkIEgu/MHrfY7O5RrLTzXDmmXPG2Fdpu78FlJS52fKpeHtv3ugMLgRpfdRmwGfQk - x9HDl1ze6LNre0YSICmRaHCuFJYUsgSw10CV538+nslB7jIV0HPDR4IVsbtG/5jeeHk8Mx8vGZSU9LxHCCpAlYLbuKxQfym - NZwd6wJodofTMhaEe+YAGu1eAZjZtV9r5bkTn4C67GAMWXYvbfXrWQhNoD7ld986gExWxVg/Vyx+GSlrjIth9tDQbeAWcPF - bFq16AGcsS8Rpe7AzuuWBqVed93Nni0k3/xP29HSp8AFgs9XzhrviX3cHiYISifO47Oc/v+6uI6oAOeB/M/YH9N27oKqJjc - VS4eV+QdrpP0K6DFzPmdixRrs3EbBF4KrgvseoSLZ68EJUk7Kb6bv+PGEEUHghDGAHdOfo4ejGX3P9oluH0boRAAAAAElFT - kSuQmCC""")) - - self.keep_case.grid(row=0, column=0, sticky=tk.NSEW, pady=2, padx=(3, 1)) diff --git a/cupcake/editor/find_replace/results.py b/cupcake/editor/find_replace/results.py deleted file mode 100644 index 19db7e6..0000000 --- a/cupcake/editor/find_replace/results.py +++ /dev/null @@ -1,21 +0,0 @@ -import tkinter as tk - - -class FindResults(tk.Label): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.init() - self.config(padx=1, width=10, font=("Helvetica", 10), bg="#252526") - - def show(self, n): - if not n: - self.config(text="No results") - self.config(fg="#f48771") - else: - self.config(text=f"{n} results") - - def init(self): - self.config(text="No results") - self.config(fg="#cccccc") \ No newline at end of file diff --git a/cupcake/editor/find_replace/toggle.py b/cupcake/editor/find_replace/toggle.py deleted file mode 100644 index 0763ed3..0000000 --- a/cupcake/editor/find_replace/toggle.py +++ /dev/null @@ -1,52 +0,0 @@ -import tkinter as tk - - -class ToggleButton(tk.Frame): - def __init__(self, master, bg, hbg, sbg, img=None, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - self.img = img - - self.hovered = False - self.state = False - - self.bg = bg - self.hbg = hbg - self.sbg = sbg - - self.imagew = tk.Label(self, image=self.img) - self.imagew.config(bg=self.bg, relief=tk.FLAT) - self.imagew.grid(row=0, column=0, sticky=tk.NS) - - self.config(bg=self.bg, pady=3, padx=3, cursor="hand2") - self.config_bindings() - - def config_bindings(self): - self.bind("", self.on_hover) - self.bind("", self.off_hover) - self.bind("", self.toggle) - - def set_image(self, img): - self.img = img - self.imagew.config(image=self.img) - - def redraw(self): - if self.state: - self.config(bg=self.sbg) - return - - if self.hovered: - self.config(bg=self.hbg) - else: - self.config(bg=self.bg) - - def toggle(self, *args): - self.state = not self.state - - def on_hover(self, *args): - self.hovered = True - self.redraw() - - def off_hover(self, *args): - self.hovered = False - self.redraw() diff --git a/cupcake/editor/find_replace/togglew.py b/cupcake/editor/find_replace/togglew.py deleted file mode 100644 index 78f6f67..0000000 --- a/cupcake/editor/find_replace/togglew.py +++ /dev/null @@ -1,61 +0,0 @@ -import tkinter as tk - - -class ToggleWidget(tk.Frame): - def __init__(self, master, img=None, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - self.img = img - - self.state = False - self.hovered = False - - self.hbg = "#363737" - self.bg = "#252526" - - self.image = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAAcAAAANC - AYAAABlyXS1AAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cua - W5rc2NhcGUub3Jnm+48GgAAAHZJREFUGJWFkLENQjEQQ1+yCTOk/ENRUFDRIEBCHzEFb - EIXewr2yNFQIEg+1/rd2T4krSVd6EwGVsDG9rkHIOkkKUYXqLUeFwHbh08gdSz2wC6ld - PsR38ADmPJgcwLui57/0w572r5Kit6HcmvtGRFzKWX7Lb4AhaddqGaZ7iQAAAAASUVOR - K5CYII=""") - self.image_toggled = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAA - AwAAAAGCAYAAAD37n+BAAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZ - QB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAKxJREFUGJV9jCFOA1EURc+tYhVogv1/sIwtg - m4Bg2AD7QpIMAQMhmXUkCIwqM6/MwJDF1FDJkjyMDMJQwLXnnOubD9GxHtVVXf8M9tXk - o5nktaSrm3f/CWXUpbAPfAKQNM0p7Y/bD9ExOzX88r2ZyllDqARtG2bIuIJeO77/qKu6 - 6+u624j4lLSIqX0MgmGtyNgA7wBe+AcOMs5b0dnEgzRoSRHxAFwknPe/eTfdaJLHIQZk - jwAAAAASUVORK5CYII=""") - - self.imagew = tk.Label(self, image=self.image) - self.imagew.config(bg=self.bg, relief=tk.FLAT, fg="#c5c5c5") - self.imagew.grid(row=0, column=0, sticky=tk.NS) - - self.config(bg=self.bg, cursor="hand2", pady=10, padx=5) - self.config_bindings() - - def config_bindings(self): - self.bind("", self.on_hover) - self.bind("", self.off_hover) - self.bind("", self.toggle) - self.imagew.bind("", self.toggle) - - def toggle(self, *args): - self.state = not self.state - if self.state: - self.imagew.config(image=self.image_toggled) - self.config(pady=30, padx=2) - else: - self.imagew.config(image=self.image) - self.config(pady=10, padx=5) - self.master.toggle_replace(self.state) - - def on_hover(self, *args): - self.hovered = True - self.imagew.config(bg=self.hbg) - self.config(bg=self.hbg) - - def off_hover(self, *args): - self.hovered = False - self.imagew.config(bg=self.bg) - self.config(bg=self.bg) diff --git a/cupcake/editor/highlighter.py b/cupcake/editor/highlighter.py index 53ecba1..6378bca 100644 --- a/cupcake/editor/highlighter.py +++ b/cupcake/editor/highlighter.py @@ -1,43 +1,50 @@ -import tkinter as tk +import os, tkinter as tk +from pygments import lex +from pygments.lexers import get_lexer_for_filename +from pygments.util import ClassNotFound class Highlighter: def __init__(self, master, *args, **kwargs): self.text = master + self.base = master.base - self.syntax = master.master.syntax + try: + self.lexer = get_lexer_for_filename(os.path.basename(master.path), inencoding=master.encoding, encoding=master.encoding) + except ClassNotFound: + self.lexer = None + + self.tag_colors = self.base.theme.syntax self.setup_highlight_tags() def setup_highlight_tags(self): - self.text.tag_configure("keywords", foreground="#559dd2") - self.text.tag_configure("strings", foreground="#cf8e7c") - self.text.tag_configure("numbers", foreground="#b5cfab") - self.text.tag_configure("comments", foreground="#699b5c") - - def highlight_pattern(self, pattern, tag, start="1.0", end=tk.END, regexp=False): - start = self.text.index(start) - end = self.text.index(end) - - self.text.mark_set("matchStart", start) - self.text.mark_set("matchEnd", start) - self.text.mark_set("searchLimit", end) - - self.text.tag_remove(tag, start, end) - - count = tk.IntVar() - while True: - index = self.text.search(pattern, "matchEnd", "searchLimit", count=count, regexp=regexp) - if index == "" or count.get() == 0: - break - - self.text.mark_set("matchStart", index) - self.text.mark_set("matchEnd", f"{index}+{count.get()}c") - - self.text.tag_add(tag, "matchStart", "matchEnd") + for token, color in self.tag_colors.items(): + self.text.tag_configure(str(token), foreground=color) - def highlight_all(self): - self.highlight_pattern(self.syntax.rgx_keywords, "keywords", regexp=True) - self.highlight_pattern(self.syntax.rgx_numbers, "numbers", regexp=True) + def highlight(self): + if not self.lexer: + return - self.highlight_pattern(self.syntax.rgx_strings, "strings", regexp=True) - self.highlight_pattern(self.syntax.rgx_comments, "comments", regexp=True) + for token, _ in self.tag_colors.items(): + self.text.tag_remove(str(token), '1.0', tk.END) + + text = self.text.get_all_text() + + # NOTE: Highlighting only visible area + # total_lines = int(self.text.index('end-1c').split('.')[0]) + # start_line = int(self.text.yview()[0] * total_lines) + # first_visible_index = f"{start_line}.0" + # last_visible_index =f"{self.text.winfo_height()}.end" + # for token, _ in self.tag_colors.items(): + # self.text.tag_remove(str(token), first_visible_index, last_visible_index) + # text = self.text.get(first_visible_index, last_visible_index) + + self.text.mark_set("range_start", '1.0') + for token, content in lex(text, self.lexer): + self.text.mark_set("range_end", f"range_start + {len(content)}c") + self.text.tag_add(str(token), "range_start", "range_end") + self.text.mark_set("range_start", "range_end") + + # DEBUG + # print(f"{content} is recognized as a <{str(token)}>") + # print("==================================") diff --git a/cupcake/editor/language/__init__.py b/cupcake/editor/language/__init__.py deleted file mode 100644 index 76f74ec..0000000 --- a/cupcake/editor/language/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .syntax import SyntaxLoader diff --git a/cupcake/editor/language/languages/__init__.py b/cupcake/editor/language/languages/__init__.py deleted file mode 100644 index 9514657..0000000 --- a/cupcake/editor/language/languages/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -languages = { - 'plain': { - 'name': 'Plain Text', - 'extensions': ["txt"], - 'executable': False, - 'autocomplete': None, - }, - 'cpp': { - 'name': 'C++', - 'extensions': ["cpp", "c", "hpp", "h"], - 'executable': True, - 'autocomplete': None, - }, -} diff --git a/cupcake/editor/language/languages/cpp.py b/cupcake/editor/language/languages/cpp.py deleted file mode 100644 index c095434..0000000 --- a/cupcake/editor/language/languages/cpp.py +++ /dev/null @@ -1,5 +0,0 @@ -class CppLSP: - def __init__(self, master, *args, **kwargs): - self.master = master - - \ No newline at end of file diff --git a/cupcake/editor/language/syntax.py b/cupcake/editor/language/syntax.py deleted file mode 100644 index 2c41b2d..0000000 --- a/cupcake/editor/language/syntax.py +++ /dev/null @@ -1,24 +0,0 @@ -from ...config.languages import CPP - - -class SyntaxLoader: - def __init__(self): - self.syntax = CPP() - self.setup_tokens() - - def setup_tokens(self): - self.keywords = self.syntax.keywords - self.numbers = self.syntax.numbers - self.strings = self.syntax.strings - self.comments = self.syntax.comments - - self.regexize_tokens() - - def regexize_tokens(self): - self.rgx_keywords = "|".join([f"\\y{i}\\y" for i in self.keywords]) - self.rgx_numbers = "|".join(self.numbers) - self.rgx_strings = "|".join(self.strings) - self.rgx_comments = "|".join(self.comments) - - def get_autocomplete_list(self): - return [(i, "keyword") for i in self.keywords] diff --git a/cupcake/editor/language/test.py b/cupcake/editor/language/test.py deleted file mode 100644 index c800483..0000000 --- a/cupcake/editor/language/test.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -[LT] Language Tools for assistance in coding - -- Detection of programming language used through extensions -- Execution of scripts -- Semantic syntax highlighting (extra) -- Smart Code completions (extra) - -""" - -from ...config import languages - - -class LanguageTools: - def __init__(self, master, *args, **kwargs): - self.master = master - - def get_language(self, filename): - """ - Get the language of a file - """ - ext = filename.split('.')[-1] - if ext in languages: - return ext - else: - return 'plain' - - def get_language_name(self, language): - """ - Get the name of a language - """ - if language in languages: - return self.languages[language]['name'] - else: - return 'Plain Text' - - def get_language_extensions(self, language): - """ - Get the extensions of a language - """ - if language in languages: - return self.languages[language]['extensions'] - else: - return [] diff --git a/cupcake/editor/linenumbers/__init__.py b/cupcake/editor/linenumbers/__init__.py index 115e636..0d066b5 100644 --- a/cupcake/editor/linenumbers/__init__.py +++ b/cupcake/editor/linenumbers/__init__.py @@ -2,89 +2,64 @@ from .breakpoint import Breakpoint +from core.components.utils import Canvas, Menubutton -class LineNumbers(tk.Frame): - def __init__(self, master, text, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.config_appearance() +class LineNumbers(Canvas): + def __init__(self, master, text=None, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.font = self.master.font + self.config(width=65, bd=0, highlightthickness=0, **self.base.theme.editors.linenumbers) self.text = text - self.cw = tk.Canvas(self) - self.cw.config(width=68, bg="#1e1e1e", highlightthickness=0) - self.cw.pack(fill=tk.BOTH, expand=True) - - def set_bar_width(self, width): - self.configure(width=width) - - def config_appearance(self): - self.font = self.master.font - self.fill = "#858585" - self.highlight_fill = "#c6c6c6" - self.config(bg="#1e1e1e") - def attach(self, text): self.text = text - def clear(self): - self.cw.delete(tk.ALL) - def mark_line(self, line): - dline = self.text.get_line_info(line) + dline = self.text.dlineinfo(line) if not dline: return y = dline[1] - btn = tk.Menubutton(self.cw, - text=">", font=("Consolas", 14), fg="#1e1e1e", bg="#1e1e1e", cursor="hand2", - activeforeground="#c5c5c5", activebackground="#1e1e1e", borderwidth=0, - width=2, height=1, pady=0, padx=0, relief=tk.FLAT) - self.cw.create_window(70, y-2, anchor=tk.NE, window=btn) - - def highlight_current_line(self): - self.mark_line(tk.INSERT) + btn = Menubutton(self, + text=">", font=("Consolas", 14), cursor="hand2", borderwidth=0, + width=2, height=1, pady=0, padx=0, relief=tk.FLAT, **self.base.theme.editors.linenumbers) + self.create_window(70, y-2, anchor=tk.NE, window=btn) - def select_line(self, line): - self.text.select_line(line) - - def redraw(self, *args): - self.clear() - self.highlight_current_line() - self.redraw_line_numbers() + def set_bar_width(self, width): + self.configure(width=width) + + def redraw(self, *_): + self.delete(tk.ALL) - def redraw_line_numbers(self): - i = self.text.get_origin() - while True: - dline = self.text.get_line_info(i) - if not dline: + i = self.text.index("@0,0") + while True : + dline = self.text.dlineinfo(i) + if dline is None: break y = dline[1] - ln = str(i).split(".")[0] + linenum = str(i).split(".")[0] - curline = self.text.get_line_info(tk.INSERT) - cur_y = None - if curline: - cur_y = curline[1] + # to highlight current line + curline = self.text.dlineinfo(tk.INSERT) + cur_y = curline[1] if curline else None if y == cur_y: - number = self.cw.create_text(46, y, anchor=tk.NE, text=ln, font=self.font, fill=self.highlight_fill, tag=i) + self.create_text(40, y, anchor=tk.NE, text=linenum, font=self.font, + fill=self.base.theme.editors.linenumbers.number.highlightforeground, tag=i) else: - number = self.cw.create_text(46, y, anchor=tk.NE, text=ln, font=self.font, fill=self.fill, tag=i) + self.create_text(40, y, anchor=tk.NE, text=linenum, font=self.font, + fill=self.base.theme.editors.linenumbers.number.foreground, tag=i) - self.cw.tag_bind(i, "", lambda _, i=i: self.select_line(i)) + self.tag_bind(i, "", lambda _, i=i: self.text.select_line(i)) - # drawing breakpoints - needs optimisations + # TODO drawing breakpoints - need optimisations # self.draw_breakpoint(y) i = self.text.index(f"{i}+1line") - - def draw_breakpoint(self, y): - bp = Breakpoint(self.cw) - self.cw.create_window(21, y-2, anchor=tk.NE, window=bp) - def toggle_breakpoint(self, y): - ... + def draw_breakpoint(self, y): + bp = Breakpoint(self) + self.create_window(21, y-2, anchor=tk.NE, window=bp) diff --git a/cupcake/editor/linenumbers/breakpoint.py b/cupcake/editor/linenumbers/breakpoint.py index 537b48b..1c0c5ca 100644 --- a/cupcake/editor/linenumbers/breakpoint.py +++ b/cupcake/editor/linenumbers/breakpoint.py @@ -5,8 +5,9 @@ class Breakpoint(tk.Label): def __init__(self, master, *args, **kwargs): super().__init__(master, *args, **kwargs) self.master = master + self.base = master.base - self.config(text="●", font=("Consolas", 14), bg="#1e1e1e", fg="#1e1e1e", cursor="hand2", + self.config(text="●", font=("Consolas", 14), fg="#1e1e1e", cursor="hand2", **self.base.theme.editors.linenumbers, borderwidth=0, width=2, height=1, pady=0, padx=0, relief=tk.FLAT) self.active = False @@ -15,7 +16,6 @@ def __init__(self, master, *args, **kwargs): def config_bindings(self): self.bind("", self.on_click) - # self.bind("", self.on_click) self.bind("", self.on_enter) self.bind("", self.on_leave) @@ -29,7 +29,6 @@ def redraw(self): else: self.config(fg="#1e1e1e") - def on_click(self, event): self.active = not self.active self.redraw() diff --git a/cupcake/editor/minimap.py b/cupcake/editor/minimap.py index 2f50ca8..3f79518 100644 --- a/cupcake/editor/minimap.py +++ b/cupcake/editor/minimap.py @@ -1,17 +1,17 @@ import tkinter as tk +from core.components.utils import Frame -class Minimap(tk.Frame): +#TODO update minimap when scrollbar is used +class Minimap(Frame): def __init__(self, master, textw, *args, **kwargs): super().__init__(master, *args, **kwargs) - self.master = master - self.tw = textw self.font = ("Arial", 1, "bold") - - self.config(bg="#252526", highlightthickness=0) - self.cw = tk.Canvas(self, bg="#1e1e1e", width=100, highlightthickness=0) - self.cw.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) + self.config(highlightthickness=0, bg=self.base.theme.border) + + self.cw = tk.Canvas(self, width=100, highlightthickness=0, **self.base.theme.editors.minimap) + self.cw.pack(fill=tk.BOTH, expand=True, side=tk.LEFT, padx=(1, 0)) self.slider_image = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAG4AAABFCAYAAACrMNMO AAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMBJRE @@ -38,13 +38,14 @@ def attach(self, textw): def redraw(self): self.cw.delete("redrawn") - self.text = self.tw.get_all_text() - self.cw.create_text(5, 0, text=self.text, anchor=tk.NW, font=self.font, fill="#678ca0", tag="redrawn") + self.text = self.tw.get('1.0', tk.END) + self.cw.create_text(5, 0, text=self.text, anchor=tk.NW, font=self.font, fill="grey", tag="redrawn") y = int(self.tw.index(tk.INSERT).split(".")[0]) * 2 - self.cw.create_line(0, y, 100, y, fill="#22374b", width=2, tag="redrawn") + self.cw.create_line(0, y, 100, y, fill="#dc8c34", width=2, tag="redrawn") self.y_bottom_lim = int(self.tw.index(tk.END).split(".")[0]) * 2 + 10 + # self.y_bottom_lim = self.tw.yview()[1] * self.cw.winfo_height() def drag_start(self, event): self._drag_data["item"] = self.cw.find_closest(event.x, event.y)[0] @@ -69,5 +70,5 @@ def drag(self, event): elif y >= self.y_bottom_lim: self.cw.move("slider", 0, -(y - self.y_bottom_lim)) - self.tw.yview(int(y / self.cw.winfo_height() * 100)) - self.tw.master.redraw_ln() + self.tw.yview(int(y / self.cw.winfo_height() * 350)) + self.tw.master.on_scroll() diff --git a/cupcake/editor/scrollbar.py b/cupcake/editor/scrollbar.py deleted file mode 100644 index f7d62c2..0000000 --- a/cupcake/editor/scrollbar.py +++ /dev/null @@ -1,67 +0,0 @@ -import tkinter as tk - - -class Scrollbar(tk.Frame): - def __init__(self, master, textw, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.tw = textw - self.font = ("Arial", 1, "bold") - - self.config(bg="#252526", highlightthickness=0, padx=1) - self.cw = tk.Canvas(self, bg="#1e1e1e", width=15, highlightthickness=0) - self.cw.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - - self.slider_image = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAG4AAABFCAYAAACrMNMO - AAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMBJRE - FUeJzt0UENwCAAwMAxLajjhwOkz8M+pMmdgiYda5/5kPPeDuAf46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo - 46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46 - KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIuyrgo46KMizIu6gNeAwIJ - 26ERewAAAABJRU5ErkJggg==""") - - self.cw.create_image(0, 0, image=self.slider_image, anchor=tk.NW, tag="slider") - - self.extra_y = 10 - self.y_top_lim = 0 - self.drag_data = {"y": 0, "item": None} - self.yvalue = 0 - - self.cw.tag_bind("slider", "", self.drag_start) - self.cw.tag_bind("slider", "", self.drag_stop) - self.cw.tag_bind("slider", "", self.drag) - - if textw: - self.redraw() - - def attach(self, textw): - self.tw = textw - - def redraw(self): - self.y_bottom_lim = int(self.tw.index(tk.END).split(".")[0]) * 2 + self.extra_y - - def drag_start(self, event): - self.drag_data["item"] = self.cw.find_closest(event.x, event.y)[0] - self.drag_data["y"] = event.y - - def drag_stop(self, event): - self.drag_data["item"] = None - self.drag_data["y"] = 0 - - def drag(self, event): - item = self.drag_data["item"] - if item != 1: - return - - delta_y = event.y - self.drag_data["y"] - self.cw.move(item, 0, delta_y) - self.drag_data["y"] = event.y - - self.yvalue = y = self.cw.coords(item)[1] - if y <= self.y_top_lim: - self.cw.move("slider", 0, -(y - self.y_top_lim)) - elif y >= self.y_bottom_lim: - self.cw.move("slider", 0, -(y - self.y_bottom_lim)) - - self.tw.yview(int(y / self.cw.winfo_height() * 100)) - self.tw.master.redraw_ln() diff --git a/cupcake/editor/syntax.py b/cupcake/editor/syntax.py new file mode 100644 index 0000000..74503a3 --- /dev/null +++ b/cupcake/editor/syntax.py @@ -0,0 +1,9 @@ +#TODO LSP implementation + +class Syntax: + def __init__(self, master): + self.master = master + self.keywords = [] + + def get_autocomplete_list(self): + return [] diff --git a/cupcake/editor/text.py b/cupcake/editor/text.py index dfa65c9..d51589b 100644 --- a/cupcake/editor/text.py +++ b/cupcake/editor/text.py @@ -1,159 +1,175 @@ -import re +import re, codecs, io +import threading import tkinter as tk +from collections import deque +from .syntax import Syntax from .highlighter import Highlighter from .autocomplete import AutoComplete -from .language import SyntaxLoader +from core.components.utils import Text -class Text(tk.Text): - def __init__(self, master, *args, **kwargs): - super().__init__(master) - self.master = master - self.font = self.master.font - self.syntax = self.master.syntax - self.pack_propagate(False) - - self.keywords = self.syntax.keywords +class Text(Text): + def __init__(self, master, path=None, exists=True, minimalist=False, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.path = path + self.data = None + self.encoding = 'utf-8' + self.exists = exists + self.minimalist = minimalist + + self.buffer_size = 1000 + self.bom = True self.current_word = None self.words = [] - self.highlighter = Highlighter(self) - - self.current_indentation = None - self.current_line = None + if self.exists: + self.load_file() + self.syntax = Syntax(self) self.auto_completion = AutoComplete( - self, items=self.syntax.get_autocomplete_list()) + self, items=self.syntax.get_autocomplete_list()) if not minimalist else None + self.highlighter = Highlighter(self) - self.create_proxy() - self.config_appearance() + self.focus_set() self.config_tags() + self.create_proxy() self.config_bindings() - - def create_proxy(self): - self._orig = self._w + "_orig" - self.tk.call("rename", self._w, self._orig) - self.tk.createcommand(self._w, self._proxy) - - def _proxy(self, *args): - cmd = (self._orig,) + args - result = self.tk.call(cmd) + self.configure(wrap=tk.NONE, relief=tk.FLAT, **self.base.theme.editors.text) - if (args[0] in ("insert", "replace", "delete") or - args[0:3] == ("mark", "set", "insert") or - args[0:2] == ("xview", "moveto") or - args[0:2] == ("xview", "scroll") or - args[0:2] == ("yview", "moveto") or - args[0:2] == ("yview", "scroll") - ): - self.event_generate("<>", when="tail") - - return result + self.update_words() - def config_appearance(self): - self.config( - font=self.master.font, bg="#1e1e1e", - fg="#d4d4d4", wrap=tk.NONE, relief=tk.FLAT, - highlightthickness=0, insertbackground="#aeafad") + def config_tags(self): + self.tag_config(tk.SEL, background=self.base.theme.primary_background_highlight) + self.tag_config("highlight", background=self.base.theme.primary_background_highlight) + self.tag_config("currentline", background=self.base.theme.border) + self.tag_config("found", background="green") + self.tag_config("foundcurrent", background="orange") def config_bindings(self): - self.bind("", self.key_release_events) + self.bind("", self.key_release_events) - self.bind("", self.select_all) + self.bind("", self.open_find_replace) self.bind("", self.multi_selection) + self.bind("", lambda e: self.handle_ctrl_hmovement()) + self.bind("", lambda e: self.handle_ctrl_hmovement(True)) + + self.bind("", self.enter_key_events) + self.bind("", self.tab_key_events) + + if self.minimalist: + return + self.bind("", self.hide_autocomplete) + self.bind("", self.hide_autocomplete) self.bind("", self.auto_completion.move_up) self.bind("", self.auto_completion.move_down) - self.bind("", - lambda e: self.handle_ctrl_hmovement()) - self.bind("", - lambda e: self.handle_ctrl_hmovement(True)) - + # self.bind("", lambda e: self.complete_pair("}")) + # self.bind("", lambda e: self.complete_pair("]")) + # self.bind("", lambda e: self.complete_pair(")")) + + # self.bind("", lambda e: self.surrounding_selection("\'")) + # self.bind("", lambda e: self.surrounding_selection("\"")) + + # self.mark_set(tk.INSERT, "insert+1c") + def key_release_events(self, event): - self.show_autocomplete(event) - self.update_current_line() + if event.keysym not in ("Up", "Down", "Return"): + self.show_autocomplete(event) match event.keysym: + # autocompletion keys + case "Button-2" | "BackSpace" | "Escape" | "Control_L" | "Control_R" | "space": + self.hide_autocomplete() + case "rightarrow" | "leftarrow": + self.update_completions() + # bracket pair completions case "braceleft": - self.complete_bracket("}") + return self.complete_pair("}") case "bracketleft": - self.complete_bracket("]") + return self.complete_pair("]") case "parenleft": - self.complete_bracket(")") + return self.complete_pair(")") # surroundings for selection case "apostrophe": - self.surrounding_selection("\'") + return self.surrounding_selection("\'") case "quotedbl": - self.surrounding_selection("\"") - - # autocompletion keys - case "Button-2" | "BackSpace" | "Escape" | "Control_L" | "Control_R" | "space": - self.hide_autocomplete() - case "rightarrow" | "leftarrow": - self.update_completions() + return self.surrounding_selection("\"") - # key events - case "Return": - self.enter_key_events() - case "Tab": - self.tab_key_events() - # extra spaces case ":" | ",": self.insert(tk.INSERT, " ") case _: pass - - def complete_bracket(self, bracket): - self.insert(tk.INSERT, bracket) - self.mark_set(tk.INSERT, "insert-1c") - - def surrounding_selection(self, char): - if self.tag_ranges(tk.SEL): - self.insert(char, tk.SEL_LAST) - self.insert(char, tk.SEL_FIRST) - - def enter_key_events(self): - if self.auto_completion.active: + + def enter_key_events(self, *_): + if not self.minimalist and self.auto_completion.active: self.auto_completion.choose() return "break" + return self.check_indentation() - - def tab_key_events(self): + + def tab_key_events(self, *_): if self.auto_completion.active: self.auto_completion.choose() return "break" - - def move_to_next_word(self): - self.mark_set(tk.INSERT, self.index("insert+1c wordend")) - - def move_to_previous_word(self): - self.mark_set(tk.INSERT, self.index("insert-1c wordstart")) - - def handle_ctrl_hmovement(self, delta=False): - if delta: - self.move_to_next_word() - else: - self.move_to_previous_word() - + + self.insert(tk.INSERT, " "*4) return "break" + + def get_all_text(self, *args): + return self.get(1.0, tk.END) - def get_all_text(self): - return self.get_all_text() - - def get_all_words(self): - return self.get_all_words() - + def get_all_text_ac(self, *args): + """ + Helper function for autocomplete.show + extracts all text except the current word. + """ + return self.get(1.0, "insert-1c wordstart-1c") + self.get("insert+1c", tk.END) + def get_current_word(self): return self.current_word.strip() + def update_words(self, *_): + if self.minimalist: + return + + self.words = list(set(re.findall(r"\w+", self.get_all_text_ac()))) + self.after(1000, self.update_words) + + def update_completions(self): + if self.minimalist: + return + + self.auto_completion.update_completions() + + def confirm_autocomplete(self, text): + self.replace_current_word(text) + + def replace_current_word(self, new_word): + if self.current_word.startswith("\n"): + self.delete("insert-1c wordstart+1c", "insert") + else: + self.delete("insert-1c wordstart", "insert") + self.insert("insert", new_word) + + def check_autocomplete_keys(self, event): + """ + Helper function for autocomplete.show to check triggers + """ + return True if event.keysym not in [ + "BackSpace", "Escape", "Return", "Tab", "space", + "Up", "Down", "Control_L", "Control_R"] else False + def cursor_screen_location(self): + """ + Helper function for autocomplete.show to detect cursor location + """ pos_x, pos_y = self.winfo_rootx(), self.winfo_rooty() cursor = tk.INSERT @@ -164,11 +180,17 @@ def cursor_screen_location(self): bbx_x, bbx_y, _, bbx_h = bbox return (pos_x + bbx_x - 1, pos_y + bbx_y + bbx_h) - def show_autocomplete(self, event): - if not self.check_autocomplete_keys(event): + def hide_autocomplete(self, *_): + if self.minimalist: return - if self.current_word.strip() not in ["{", "}", ":", "", None, "\""]: + self.auto_completion.hide() + + def show_autocomplete(self, event): + if self.minimalist or not self.check_autocomplete_keys(event): + return + + if self.current_word.strip() not in ["{", "}", ":", "", None, "\""] and not self.current_word.strip()[0].isdigit(): if not self.auto_completion.active: if event.keysym in ["Left", "Right"]: return @@ -180,37 +202,40 @@ def show_autocomplete(self, event): else: if self.auto_completion.active: self.hide_autocomplete() - - def check_autocomplete_keys(self, event): - return True if event.keysym not in [ - "BackSpace", "Escape", "Return", "Tab", "space", - "Up", "Down", "Control_L", "Control_R"] else False - def update_completion_words(self): - self.auto_completion.update_all_words() - - def update_completions(self): - self.auto_completion.update_completions() - - def hide_autocomplete(self): - self.auto_completion.hide() + def complete_pair(self, char): + self.insert(tk.INSERT, char) + self.mark_set(tk.INSERT, "insert-1c") - def move_cursor(self, position): - self.mark_set(tk.INSERT, position) + def surrounding_selection(self, char): + next_char = self.get("insert", "insert+1c") + if next_char == char: + self.mark_set(tk.INSERT, "insert+1c") + self.delete("insert-1c", "insert") + return "break" - def clear_all_selection(self): - self.tag_remove(tk.SEL, 1.0, tk.END) - - def select_line(self, line): - self.clear_all_selection() + if self.tag_ranges(tk.SEL): + self.insert(char, tk.SEL_LAST) + self.insert(char, tk.SEL_FIRST) + return - line = int(line.split(".")[0]) - start = str(float(line)) - end = str(float(line + 1)) - self.tag_add(tk.SEL, start, end) + self.complete_pair(char) + return "break" + + def move_to_next_word(self): + self.mark_set(tk.INSERT, self.index("insert+1c wordend")) + + def move_to_previous_word(self): + self.mark_set(tk.INSERT, self.index("insert-1c wordstart")) + + def handle_ctrl_hmovement(self, delta=False): + if delta: + self.move_to_next_word() + else: + self.move_to_previous_word() + + return "break" - self.move_cursor(end) - def update_current_indent(self): line = self.get("insert linestart", "insert lineend") match = re.match(r'^(\s+)', line) @@ -223,9 +248,6 @@ def update_current_line(self): def add_newline(self, count=1): self.insert(tk.INSERT, "\n" * count) - def confirm_autocomplete(self, text): - self.replace_current_word(text) - def check_indentation(self, *args): self.update_current_indent() if self.update_current_line(): @@ -241,77 +263,254 @@ def check_indentation(self, *args): return "break" - def get_origin(self): - return self.index("@0,0") - - def get_line_info(self, line): - return self.dlineinfo(line) - - def clear_insert(self, content): - self.delete(1.0, tk.END) - self.insert(1.0, content) - - def load_file(self, path): - with open(path, 'r') as fp: - self.clear_insert(fp.read()) - - self.mark_set(tk.INSERT, 1.0) - - def select_all(self, *args): - self.tag_remove("highlight", 1.0, tk.END) - - self.tag_add(tk.SEL, 1.0, tk.END) - - # scroll to top - # self.mark_set(tk.INSERT, 1.0) - # self.see(tk.INSERT) - - return "break" - # def handle_space(self, *args): # self.insert(tk.INSERT, "-") - # return "break" def multi_selection(self, *args): #TODO: multi cursor editing - return "break" - def replace_current_word(self, new_word): - if self.current_word.startswith("\n"): - self.delete("insert-1c wordstart+1c", "insert") - else: - self.delete("insert-1c wordstart", "insert") - self.insert("insert", new_word) + def open_find_replace(self, *_): + self.base.findreplace.show(self) + + def detect_encoding(self, file_path): + with open(file_path, 'rb') as file: + bom = file.read(4) + + if bom.startswith(codecs.BOM_UTF8): + return 'utf-8' + elif bom.startswith(codecs.BOM_LE) or bom.startswith(codecs.BOM_BE): + return 'utf-16' + elif bom.startswith(codecs.BOM32_BE) or bom.startswith(codecs.BOM32_LE): + return 'utf-32' + + self.bom = False + return 'utf-8' + + def load_file(self): + try: + encoding = self.detect_encoding(self.path) + file = open(self.path, 'r', encoding=encoding) + self.encoding = encoding + def load_file(): + while True: + chunk = file.read(self.buffer_size) + if not chunk: + file.close() + break + self.write(chunk) + self.update() + self.master.on_change() + self.master.on_scroll() + threading.Thread(target=load_file).start() + except Exception as e: + self.master.unsupported_file() - def get_all_text(self, *args): + def save_file(self, path=None): + if path: + try: + with open(path, 'w') as fp: + fp.write(self.get_all_text()) + except Exception: + return + + self.path = path + #TODO update tab name + + try: + with open(self.path, 'w') as fp: + fp.write(self.get_all_text()) + except Exception: + return + + def copy(self, *_): + self.event_generate("<>") + + def cut(self, *_): + self.event_generate("<>") + + def paste(self, *_): + self.event_generate("<>") + + def set_data(self, data): + self.data = data + + def clear_insert(self, text=None): + self.clear() + + def write_with_buffer(): + buffer = deque(maxlen=self.buffer_size) + for char in text: + buffer.append(char) + if len(buffer) >= self.buffer_size: + chunk = ''.join(buffer) + self.write(chunk) + self.update() + buffer.clear() + if buffer: + chunk = ''.join(buffer) + self.write(chunk) + self.update() + + threading.Thread(target=write_with_buffer).start() + + def clear(self): + self.delete(1.0, tk.END) + + def write(self, text, *args): + self.insert(tk.END, text, *args) + + def newline(self, *args): + self.write("\n", *args) + + def get_all_text(self): return self.get(1.0, tk.END) + + def get_selected_text(self): + try: + return self.selection_get() + except Exception: + return "" + + def add_newline(self, count=1): + self.insert(tk.INSERT, "\n" * count) - def get_all_text_ac(self, *args): - return self.get(1.0, "insert-1c wordstart-1c") + self.get("insert+1c", tk.END) + def get_selected_count(self): + return len(self.get_selected_text()) + + @property + def line(self): + return int(self.index(tk.INSERT).split('.')[0]) + + @property + def column(self): + return int(self.index(tk.INSERT).split('.')[1]) + 1 + + @property + def position(self): + lc = self.index(tk.INSERT).split('.') + return [lc[0], int(lc[1]) + 1] + + def scroll_to_end(self): + self.mark_set(tk.INSERT, tk.END) + self.see(tk.INSERT) + + def scroll_to_start(self): + self.mark_set(tk.INSERT, 1.0) + self.see(tk.INSERT) + + def scroll_to_line(self, line): + self.mark_set(tk.INSERT, line) + self.see(tk.INSERT) - def get_all_words(self, *args): - return self.words + def set_wrap(self, flag=True): + if flag: + self.configure(wrap=tk.WORD) + else: + self.configure(wrap=tk.NONE) + + def set_active(self, flag=True): + if flag: + self.configure(state=tk.NORMAL) + else: + self.configure(state=tk.DISABLED) + + def show_unsupported_dialog(self): + self.set_wrap(True) + self.configure(font=('Arial', 10), padx=10, pady=10) + self.write("This file is not displayed in this editor because it is either binary or uses an unsupported text encoding.") + self.set_active(False) - def update_words(self): - self.words = re.findall(r"\w+", self.get_all_text_ac()) - self.update_completion_words() + def move_cursor(self, position): + self.mark_set(tk.INSERT, position) + def clear_all_selection(self): + self.tag_remove(tk.SEL, 1.0, tk.END) + + def highlight_current_line(self, *_): + self.tag_remove("currentline", 1.0, tk.END) + + line = int(self.index(tk.INSERT).split(".")[0]) + start = str(float(line)) + end = str(float(line + 1)) + self.tag_add("currentline", start, end) + + def select_line(self, line): + self.clear_all_selection() + + line = int(line.split(".")[0]) + start = str(float(line)) + end = str(float(line + 1)) + self.tag_add(tk.SEL, start, end) + + self.move_cursor(end) + def highlight_current_word(self): + if self.minimalist: + return + self.tag_remove("highlight", 1.0, tk.END) - text = self.get("insert wordstart", "insert wordend") - word = re.findall(r"\w+", text) - if any(word): - if word[0] not in self.keywords: - self.highlighter.highlight_pattern(f"\\y{word[0]}\\y", "highlight", regexp=True) + try: + if word := self.get_selected_text(): + self.highlight_pattern(word, "highlight", end="insert-1c wordstart-1c") + self.highlight_pattern(word, start="insert+1c") + else: + word = re.findall(r"\w+", self.get("insert wordstart", "insert wordend")) + if any(word) and word[0] not in self.syntax.keywords: + self.highlight_pattern(f"\\y{word[0]}\\y", "highlight", regexp=True) + except: + pass + + def highlight_pattern(self, pattern, tag, start="1.0", end=tk.END, regexp=False): + start = self.index(start) + end = self.index(end) + + self.mark_set("matchStart", start) + self.mark_set("matchEnd", start) + self.mark_set("searchLimit", end) + + self.tag_remove(tag, start, end) + + count = tk.IntVar() + while True: + index = self.search(pattern, "matchEnd", "searchLimit", count=count, regexp=regexp) + if index == "" or count.get() == 0: + break + + self.mark_set("matchStart", index) + self.mark_set("matchEnd", f"{index}+{count.get()}c") + + self.tag_add(tag, "matchStart", "matchEnd") - def on_change(self, *args): + def refresh(self, *args): + if self.minimalist: + return + self.current_word = self.get("insert-1c wordstart", "insert") - self.update_words() + self.highlighter.highlight() self.highlight_current_word() - - - def config_tags(self): - self.tag_config(tk.SEL, background="#264f78", foreground="#d4d4d4") - self.tag_config("highlight", background="#464646", foreground="#d4d4d4") + self.highlight_current_line() + + def create_proxy(self): + self._orig = self._w + "_orig" + self.tk.call("rename", self._w, self._orig) + self.tk.createcommand(self._w, self._proxy) + + def _proxy(self, *args): + if args[0] == 'get' and (args[1] == tk.SEL_FIRST and args[2] == tk.SEL_LAST) and not self.tag_ranges(tk.SEL): + return + if args[0] == 'delete' and (args[1] == tk.SEL_FIRST and args[2] == tk.SEL_LAST) and not self.tag_ranges(tk.SEL): + return + + cmd = (self._orig,) + args + result = self.tk.call(cmd) + + if (args[0] in ("insert", "replace", "delete") or args[0:3] == ("mark", "set", "insert")): + self.event_generate("<>", when="tail") + + elif (args[0:2] == ("xview", "moveto") or args[0:2] == ("yview", "moveto") or + args[0:2] == ("xview", "scroll") or args[0:2] == ("yview", "scroll")): + self.event_generate("<>", when="tail") + + return result diff --git a/cupcake/editor/textw.py b/cupcake/editor/textw.py deleted file mode 100644 index a17d493..0000000 --- a/cupcake/editor/textw.py +++ /dev/null @@ -1,86 +0,0 @@ -import re, tkinter as tk - - -class TextW(tk.Text): - def __init__(self, master, *args, **kwargs): - super().__init__(master, *args, **kwargs) - self.master = master - - self.keywords = master.syntax.keywords - self.current_word = None - self.words = [] - - self._orig = self._w + "_orig" - self.tk.call("rename", self._w, self._orig) - self.tk.createcommand(self._w, self._proxy) - - self.config_appearance() - self.config_tags() - self.config_bindings() - - def multi_selection(self, *args): - #TODO: multi cursor editing - - return "break" - - def replace_current_word(self, new_word): - if self.current_word.startswith("\n"): - self.delete("insert-1c wordstart+1c", "insert") - else: - self.delete("insert-1c wordstart", "insert") - self.insert("insert", new_word) - - def get_all_text(self, *args): - return self.get(1.0, tk.END) - - def get_all_text_ac(self, *args): - return self.get(1.0, "insert-1c wordstart-1c") + self.get("insert+1c", tk.END) - - def get_all_words(self, *args): - return self.words - - def update_words(self): - self.words = re.findall(r"\w+", self.get_all_text_ac()) - self.master.update_completion_words() - - def highlight_current_word(self): - self.tag_remove("highlight", 1.0, tk.END) - text = self.get("insert wordstart", "insert wordend") - word = re.findall(r"\w+", text) - if any(word): - if word[0] not in self.keywords: - self.master.highlighter.highlight_pattern(f"\\y{word[0]}\\y", "highlight", regexp=True) - - def on_change(self, *args): - self.current_word = self.get("insert-1c wordstart", "insert") - self.update_words() - self.highlight_current_word() - - def config_appearance(self): - self.config( - font=self.master.font, bg="#1e1e1e", - fg="#d4d4d4", wrap=tk.NONE, relief=tk.FLAT, - highlightthickness=0, insertbackground="#aeafad") - - def config_tags(self): - self.tag_config(tk.SEL, background="#264f78", foreground="#d4d4d4") - self.tag_config("highlight", background="#464646", foreground="#d4d4d4") - - def config_bindings(self): - self.bind("", self.master.select_all) - self.bind("", self.multi_selection) - - def _proxy(self, *args): - cmd = (self._orig,) + args - result = self.tk.call(cmd) - - if (args[0] in ("insert", "replace", "delete") or - args[0:3] == ("mark", "set", "insert") or - args[0:2] == ("xview", "moveto") or - args[0:2] == ("xview", "scroll") or - args[0:2] == ("yview", "moveto") or - args[0:2] == ("yview", "scroll") - ): - self.event_generate("<>", when="tail") - - return result \ No newline at end of file diff --git a/cupcake/utils/__init__.py b/cupcake/utils/__init__.py new file mode 100644 index 0000000..375e18c --- /dev/null +++ b/cupcake/utils/__init__.py @@ -0,0 +1,28 @@ +""" +Various types of widgets/functions used across the editor +""" + +from .button import Button +from .bubble import Bubble +from .buttonsentry import ButtonsEntry +from .canvas import Canvas +from .codicon import get_codicon +from .colorizer import colorize +from .frame import Frame +from .filetype import FileType +from .icon import Icon +from .iconbutton import IconButton +from .label import WrappingLabel, Label, TruncatedLabel +from .scrollbar import Scrollbar +from .scrollableframe import ScrollableFrame +from .tree import Tree +from .shortcut import Shortcut +from .entry import Entry +from .toplevel import Toplevel +from .menubutton import Menubutton +from .text import Text +from .iconlabelbutton import IconLabelButton + +@staticmethod +def clamp(value, min_val, max_val): + return min(max(min_val, value), max_val) diff --git a/cupcake/utils/bubble.py b/cupcake/utils/bubble.py new file mode 100644 index 0000000..bfd04b5 --- /dev/null +++ b/cupcake/utils/bubble.py @@ -0,0 +1,30 @@ +import tkinter as tk + +from .toplevel import Toplevel +from .label import Label + + +class Bubble(Toplevel): + """ + +===============+ + || Text || + +===============+ + """ + def __init__(self, master, text, bd=1, *args, **kw): + super().__init__(master, *args, **kw) + self.overrideredirect(True) + self.config(bg=self.base.theme.border) + Label(self, text=text, padx=5, pady=5, font=("Segoi UI", 10), **self.base.theme.utils.bubble).pack(padx=bd, pady=bd) + self.withdraw() + + def get_pos(self): + return (f"+{self.master.winfo_rootx() + self.master.winfo_width() + 5}" + + f"+{int(self.master.winfo_rooty() + (self.master.winfo_height() - self.winfo_height())/2)}") + + def show(self, *_): + self.update_idletasks() + self.geometry(self.get_pos()) + self.deiconify() + + def hide(self, *_): + self.withdraw() diff --git a/cupcake/utils/button.py b/cupcake/utils/button.py new file mode 100644 index 0000000..65b1f2e --- /dev/null +++ b/cupcake/utils/button.py @@ -0,0 +1,13 @@ +import tkinter as tk + +from .menubutton import Menubutton + + +class Button(Menubutton): + """ + A Flat style button + """ + def __init__(self, master, text, command=lambda _: None, *args, **kwargs): + super().__init__(master, text=text, *args, **kwargs) + self.config(pady=5, font=("Segoi UI", 10), **self.base.theme.utils.button) + self.bind('', command) diff --git a/cupcake/utils/buttonsentry.py b/cupcake/utils/buttonsentry.py new file mode 100644 index 0000000..115cba8 --- /dev/null +++ b/cupcake/utils/buttonsentry.py @@ -0,0 +1,39 @@ +import tkinter as tk + +from .frame import Frame +from .iconbutton import IconButton + +from hintedtext import HintedEntry + + +class ButtonsEntry(Frame): + def __init__(self, master, hint="", buttons=(), textvariable=None, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.config(padx=1, pady=1, bg=self.base.theme.border) + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + + self.entry = HintedEntry(self, relief=tk.FLAT, font=("Segoi UI", 10), bd=5, hint=hint, **self.base.theme.utils.buttonsentry, textvariable=textvariable) + self.entry.grid(row=0, column=0, sticky=tk.NSEW) + + self.column = 1 + self.add_buttons(buttons) + + def add_button(self, icon, event=lambda _: None): + b=IconButton(self, icon, event) + b.grid(row=0, column=self.column, sticky='') + b.config(**self.base.theme.utils.buttonsentry.button) + self.column += 1 + + def add_buttons(self, buttons): + for btn in buttons: + self.add_button(*btn) + + def get(self, *args): + return self.entry.get(*args) + + def delete(self, *args): + return self.entry.delete(*args) + + def insert(self, *args): + return self.entry.insert(*args) diff --git a/cupcake/utils/canvas.py b/cupcake/utils/canvas.py new file mode 100644 index 0000000..ee7418b --- /dev/null +++ b/cupcake/utils/canvas.py @@ -0,0 +1,11 @@ +import tkinter as tk + + +class Canvas(tk.Canvas): + """ + normal canvas with reference to base + """ + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base diff --git a/cupcake/utils/codicon.py b/cupcake/utils/codicon.py new file mode 100644 index 0000000..72d2b9b --- /dev/null +++ b/cupcake/utils/codicon.py @@ -0,0 +1,525 @@ +codicons = { + "add": "\uea60", + "plus": "\uea60", + "gist-new": "\uea60", + "repo-create": "\uea60", + "lightbulb": "\uea61", + "light-bulb": "\uea61", + "repo": "\uea62", + "repo-delete": "\uea62", + "gist-fork": "\uea63", + "repo-forked": "\uea63", + "git-pull-request": "\uea64", + "git-pull-request-abandoned": "\uea64", + "record-keys": "\uea65", + "keyboard": "\uea65", + "tag": "\uea66", + "tag-add": "\uea66", + "tag-remove": "\uea66", + "person": "\uea67", + "person-follow": "\uea67", + "person-outline": "\uea67", + "person-filled": "\uea67", + "git-branch": "\uea68", + "git-branch-create": "\uea68", + "git-branch-delete": "\uea68", + "source-control": "\uea68", + "mirror": "\uea69", + "mirror-public": "\uea69", + "star": "\uea6a", + "star-add": "\uea6a", + "star-delete": "\uea6a", + "star-empty": "\uea6a", + "comment": "\uea6b", + "comment-add": "\uea6b", + "alert": "\uea6c", + "warning": "\uea6c", + "search": "\uea6d", + "search-save": "\uea6d", + "log-out": "\uea6e", + "sign-out": "\uea6e", + "log-in": "\uea6f", + "sign-in": "\uea6f", + "eye": "\uea70", + "eye-unwatch": "\uea70", + "eye-watch": "\uea70", + "circle-filled": "\uea71", + "primitive-dot": "\uea71", + "close-dirty": "\uea71", + "debug-breakpoint": "\uea71", + "debug-breakpoint-disabled": "\uea71", + "debug-hint": "\uea71", + "terminal-decoration-success": "\uea71", + "primitive-square": "\uea72", + "edit": "\uea73", + "pencil": "\uea73", + "info": "\uea74", + "issue-opened": "\uea74", + "gist-private": "\uea75", + "git-fork-private": "\uea75", + "lock": "\uea75", + "mirror-private": "\uea75", + "close": "\uea76", + "remove-close": "\uea76", + "x": "\uea76", + "repo-sync": "\uea77", + "sync": "\uea77", + "clone": "\uea78", + "desktop-download": "\uea78", + "beaker": "\uea79", + "microscope": "\uea79", + "vm": "\uea7a", + "device-desktop": "\uea7a", + "file": "\uea7b", + "file-text": "\uea7b", + "more": "\uea7c", + "ellipsis": "\uea7c", + "kebab-horizontal": "\uea7c", + "mail-reply": "\uea7d", + "reply": "\uea7d", + "organization": "\uea7e", + "organization-filled": "\uea7e", + "organization-outline": "\uea7e", + "new-file": "\uea7f", + "file-add": "\uea7f", + "new-folder": "\uea80", + "file-directory-create": "\uea80", + "trash": "\uea81", + "trashcan": "\uea81", + "history": "\uea82", + "clock": "\uea82", + "folder": "\uea83", + "file-directory": "\uea83", + "symbol-folder": "\uea83", + "logo-github": "\uea84", + "mark-github": "\uea84", + "github": "\uea84", + "terminal": "\uea85", + "console": "\uea85", + "repl": "\uea85", + "zap": "\uea86", + "symbol-event": "\uea86", + "error": "\uea87", + "stop": "\uea87", + "variable": "\uea88", + "symbol-variable": "\uea88", + "array": "\uea8a", + "symbol-array": "\uea8a", + "symbol-module": "\uea8b", + "symbol-package": "\uea8b", + "symbol-namespace": "\uea8b", + "symbol-object": "\uea8b", + "symbol-method": "\uea8c", + "symbol-function": "\uea8c", + "symbol-constructor": "\uea8c", + "symbol-boolean": "\uea8f", + "symbol-null": "\uea8f", + "symbol-numeric": "\uea90", + "symbol-number": "\uea90", + "symbol-structure": "\uea91", + "symbol-struct": "\uea91", + "symbol-parameter": "\uea92", + "symbol-type-parameter": "\uea92", + "symbol-key": "\uea93", + "symbol-text": "\uea93", + "symbol-reference": "\uea94", + "go-to-file": "\uea94", + "symbol-enum": "\uea95", + "symbol-value": "\uea95", + "symbol-ruler": "\uea96", + "symbol-unit": "\uea96", + "activate-breakpoints": "\uea97", + "archive": "\uea98", + "arrow-both": "\uea99", + "arrow-down": "\uea9a", + "arrow-left": "\uea9b", + "arrow-right": "\uea9c", + "arrow-small-down": "\uea9d", + "arrow-small-left": "\uea9e", + "arrow-small-right": "\uea9f", + "arrow-small-up": "\ueaa0", + "arrow-up": "\ueaa1", + "bell": "\ueaa2", + "bold": "\ueaa3", + "book": "\ueaa4", + "bookmark": "\ueaa5", + "debug-breakpoint-conditional-unverified": "\ueaa6", + "debug-breakpoint-conditional": "\ueaa7", + "debug-breakpoint-conditional-disabled": "\ueaa7", + "debug-breakpoint-data-unverified": "\ueaa8", + "debug-breakpoint-data": "\ueaa9", + "debug-breakpoint-data-disabled": "\ueaa9", + "debug-breakpoint-log-unverified": "\ueaaa", + "debug-breakpoint-log": "\ueaab", + "debug-breakpoint-log-disabled": "\ueaab", + "briefcase": "\ueaac", + "broadcast": "\ueaad", + "browser": "\ueaae", + "bug": "\ueaaf", + "calendar": "\ueab0", + "case-sensitive": "\ueab1", + "check": "\ueab2", + "checklist": "\ueab3", + "chevron-down": "\ueab4", + "chevron-left": "\ueab5", + "chevron-right": "\ueab6", + "chevron-up": "\ueab7", + "chrome-close": "\ueab8", + "chrome-maximize": "\ueab9", + "chrome-minimize": "\ueaba", + "chrome-restore": "\ueabb", + "circle-outline": "\ueabc", + "circle": "\ueabc", + "debug-breakpoint-unverified": "\ueabc", + "terminal-decoration-incomplete": "\ueabc", + "circle-slash": "\ueabd", + "circuit-board": "\ueabe", + "clear-all": "\ueabf", + "clippy": "\ueac0", + "close-all": "\ueac1", + "cloud-download": "\ueac2", + "cloud-upload": "\ueac3", + "code": "\ueac4", + "collapse-all": "\ueac5", + "color-mode": "\ueac6", + "comment-discussion": "\ueac7", + "credit-card": "\ueac9", + "dash": "\ueacc", + "dashboard": "\ueacd", + "database": "\ueace", + "debug-continue": "\ueacf", + "debug-disconnect": "\uead0", + "debug-pause": "\uead1", + "debug-restart": "\uead2", + "debug-start": "\uead3", + "debug-step-into": "\uead4", + "debug-step-out": "\uead5", + "debug-step-over": "\uead6", + "debug-stop": "\uead7", + "debug": "\uead8", + "device-camera-video": "\uead9", + "device-camera": "\ueada", + "device-mobile": "\ueadb", + "diff-added": "\ueadc", + "diff-ignored": "\ueadd", + "diff-modified": "\ueade", + "diff-removed": "\ueadf", + "diff-renamed": "\ueae0", + "diff": "\ueae1", + "discard": "\ueae2", + "editor-layout": "\ueae3", + "empty-window": "\ueae4", + "exclude": "\ueae5", + "extensions": "\ueae6", + "eye-closed": "\ueae7", + "file-binary": "\ueae8", + "file-code": "\ueae9", + "file-media": "\ueaea", + "file-pdf": "\ueaeb", + "file-submodule": "\ueaec", + "file-symlink-directory": "\ueaed", + "file-symlink-file": "\ueaee", + "file-zip": "\ueaef", + "files": "\ueaf0", + "filter": "\ueaf1", + "flame": "\ueaf2", + "fold-down": "\ueaf3", + "fold-up": "\ueaf4", + "fold": "\ueaf5", + "folder-active": "\ueaf6", + "folder-opened": "\ueaf7", + "gear": "\ueaf8", + "gift": "\ueaf9", + "gist-secret": "\ueafa", + "gist": "\ueafb", + "git-commit": "\ueafc", + "git-compare": "\ueafd", + "compare-changes": "\ueafd", + "git-merge": "\ueafe", + "github-action": "\ueaff", + "github-alt": "\ueb00", + "globe": "\ueb01", + "grabber": "\ueb02", + "graph": "\ueb03", + "gripper": "\ueb04", + "heart": "\ueb05", + "home": "\ueb06", + "horizontal-rule": "\ueb07", + "hubot": "\ueb08", + "inbox": "\ueb09", + "issue-reopened": "\ueb0b", + "issues": "\ueb0c", + "italic": "\ueb0d", + "jersey": "\ueb0e", + "json": "\ueb0f", + "kebab-vertical": "\ueb10", + "key": "\ueb11", + "law": "\ueb12", + "lightbulb-autofix": "\ueb13", + "link-external": "\ueb14", + "link": "\ueb15", + "list-ordered": "\ueb16", + "list-unordered": "\ueb17", + "live-share": "\ueb18", + "loading": "\ueb19", + "location": "\ueb1a", + "mail-read": "\ueb1b", + "mail": "\ueb1c", + "markdown": "\ueb1d", + "megaphone": "\ueb1e", + "mention": "\ueb1f", + "milestone": "\ueb20", + "mortar-board": "\ueb21", + "move": "\ueb22", + "multiple-windows": "\ueb23", + "mute": "\ueb24", + "no-newline": "\ueb25", + "note": "\ueb26", + "octoface": "\ueb27", + "open-preview": "\ueb28", + "package": "\ueb29", + "paintcan": "\ueb2a", + "pin": "\ueb2b", + "play": "\ueb2c", + "run": "\ueb2c", + "plug": "\ueb2d", + "preserve-case": "\ueb2e", + "preview": "\ueb2f", + "project": "\ueb30", + "pulse": "\ueb31", + "question": "\ueb32", + "quote": "\ueb33", + "radio-tower": "\ueb34", + "reactions": "\ueb35", + "references": "\ueb36", + "refresh": "\ueb37", + "regex": "\ueb38", + "remote-explorer": "\ueb39", + "remote": "\ueb3a", + "remove": "\ueb3b", + "replace-all": "\ueb3c", + "replace": "\ueb3d", + "repo-clone": "\ueb3e", + "repo-force-push": "\ueb3f", + "repo-pull": "\ueb40", + "repo-push": "\ueb41", + "report": "\ueb42", + "request-changes": "\ueb43", + "rocket": "\ueb44", + "root-folder-opened": "\ueb45", + "root-folder": "\ueb46", + "rss": "\ueb47", + "ruby": "\ueb48", + "save-all": "\ueb49", + "save-as": "\ueb4a", + "save": "\ueb4b", + "screen-full": "\ueb4c", + "screen-normal": "\ueb4d", + "search-stop": "\ueb4e", + "server": "\ueb50", + "settings-gear": "\ueb51", + "settings": "\ueb52", + "shield": "\ueb53", + "smiley": "\ueb54", + "sort-precedence": "\ueb55", + "split-horizontal": "\ueb56", + "split-vertical": "\ueb57", + "squirrel": "\ueb58", + "star-full": "\ueb59", + "star-half": "\ueb5a", + "symbol-class": "\ueb5b", + "symbol-color": "\ueb5c", + "symbol-constant": "\ueb5d", + "symbol-enum-member": "\ueb5e", + "symbol-field": "\ueb5f", + "symbol-file": "\ueb60", + "symbol-interface": "\ueb61", + "symbol-keyword": "\ueb62", + "symbol-misc": "\ueb63", + "symbol-operator": "\ueb64", + "symbol-property": "\ueb65", + "wrench": "\ueb65", + "wrench-subaction": "\ueb65", + "symbol-snippet": "\ueb66", + "tasklist": "\ueb67", + "telescope": "\ueb68", + "text-size": "\ueb69", + "three-bars": "\ueb6a", + "thumbsdown": "\ueb6b", + "thumbsup": "\ueb6c", + "tools": "\ueb6d", + "triangle-down": "\ueb6e", + "triangle-left": "\ueb6f", + "triangle-right": "\ueb70", + "triangle-up": "\ueb71", + "twitter": "\ueb72", + "unfold": "\ueb73", + "unlock": "\ueb74", + "unmute": "\ueb75", + "unverified": "\ueb76", + "verified": "\ueb77", + "versions": "\ueb78", + "vm-active": "\ueb79", + "vm-outline": "\ueb7a", + "vm-running": "\ueb7b", + "watch": "\ueb7c", + "whitespace": "\ueb7d", + "whole-word": "\ueb7e", + "window": "\ueb7f", + "word-wrap": "\ueb80", + "zoom-in": "\ueb81", + "zoom-out": "\ueb82", + "list-filter": "\ueb83", + "list-flat": "\ueb84", + "list-selection": "\ueb85", + "selection": "\ueb85", + "list-tree": "\ueb86", + "debug-breakpoint-function-unverified": "\ueb87", + "debug-breakpoint-function": "\ueb88", + "debug-breakpoint-function-disabled": "\ueb88", + "debug-stackframe-active": "\ueb89", + "circle-small-filled": "\ueb8a", + "debug-stackframe-dot": "\ueb8a", + "terminal-decoration-mark": "\ueb8a", + "debug-stackframe": "\ueb8b", + "debug-stackframe-focused": "\ueb8b", + "debug-breakpoint-unsupported": "\ueb8c", + "symbol-string": "\ueb8d", + "debug-reverse-continue": "\ueb8e", + "debug-step-back": "\ueb8f", + "debug-restart-frame": "\ueb90", + "debug-alt": "\ueb91", + "call-incoming": "\ueb92", + "call-outgoing": "\ueb93", + "menu": "\ueb94", + "expand-all": "\ueb95", + "feedback": "\ueb96", + "group-by-ref-type": "\ueb97", + "ungroup-by-ref-type": "\ueb98", + "account": "\ueb99", + "bell-dot": "\ueb9a", + "debug-console": "\ueb9b", + "library": "\ueb9c", + "output": "\ueb9d", + "run-all": "\ueb9e", + "sync-ignored": "\ueb9f", + "pinned": "\ueba0", + "github-inverted": "\ueba1", + "server-process": "\ueba2", + "server-environment": "\ueba3", + "pass": "\ueba4", + "issue-closed": "\ueba4", + "stop-circle": "\ueba5", + "play-circle": "\ueba6", + "record": "\ueba7", + "debug-alt-small": "\ueba8", + "vm-connect": "\ueba9", + "cloud": "\uebaa", + "merge": "\uebab", + "export": "\uebac", + "graph-left": "\uebad", + "magnet": "\uebae", + "notebook": "\uebaf", + "redo": "\uebb0", + "check-all": "\uebb1", + "pinned-dirty": "\uebb2", + "pass-filled": "\uebb3", + "circle-large-filled": "\uebb4", + "circle-large": "\uebb5", + "circle-large-outline": "\uebb5", + "combine": "\uebb6", + "gather": "\uebb6", + "table": "\uebb7", + "variable-group": "\uebb8", + "type-hierarchy": "\uebb9", + "type-hierarchy-sub": "\uebba", + "type-hierarchy-super": "\uebbb", + "git-pull-request-create": "\uebbc", + "run-above": "\uebbd", + "run-below": "\uebbe", + "notebook-template": "\uebbf", + "debug-rerun": "\uebc0", + "workspace-trusted": "\uebc1", + "workspace-untrusted": "\uebc2", + "workspace-unknown": "\uebc3", + "terminal-cmd": "\uebc4", + "terminal-debian": "\uebc5", + "terminal-linux": "\uebc6", + "terminal-powershell": "\uebc7", + "terminal-tmux": "\uebc8", + "terminal-ubuntu": "\uebc9", + "terminal-bash": "\uebca", + "arrow-swap": "\uebcb", + "copy": "\uebcc", + "person-add": "\uebcd", + "filter-filled": "\uebce", + "wand": "\uebcf", + "debug-line-by-line": "\uebd0", + "inspect": "\uebd1", + "layers": "\uebd2", + "layers-dot": "\uebd3", + "layers-active": "\uebd4", + "compass": "\uebd5", + "compass-dot": "\uebd6", + "compass-active": "\uebd7", + "azure": "\uebd8", + "issue-draft": "\uebd9", + "git-pull-request-closed": "\uebda", + "git-pull-request-draft": "\uebdb", + "debug-all": "\uebdc", + "debug-coverage": "\uebdd", + "run-errors": "\uebde", + "folder-library": "\uebdf", + "debug-continue-small": "\uebe0", + "beaker-stop": "\uebe1", + "graph-line": "\uebe2", + "graph-scatter": "\uebe3", + "pie-chart": "\uebe4", + "bracket": "\ueb0f", + "bracket-dot": "\uebe5", + "bracket-error": "\uebe6", + "lock-small": "\uebe7", + "azure-devops": "\uebe8", + "verified-filled": "\uebe9", + "newline": "\uebea", + "layout": "\uebeb", + "layout-activitybar-left": "\uebec", + "layout-activitybar-right": "\uebed", + "layout-panel-left": "\uebee", + "layout-panel-center": "\uebef", + "layout-panel-justify": "\uebf0", + "layout-panel-right": "\uebf1", + "layout-panel": "\uebf2", + "layout-sidebar-left": "\uebf3", + "layout-sidebar-right": "\uebf4", + "layout-statusbar": "\uebf5", + "layout-menubar": "\uebf6", + "layout-centered": "\uebf7", + "target": "\uebf8", + "indent": "\uebf9", + "record-small": "\uebfa", + "error-small": "\uebfb", + "terminal-decoration-error": "\uebfb", + "arrow-circle-down": "\uebfc", + "arrow-circle-left": "\uebfd", + "arrow-circle-right": "\uebfe", + "arrow-circle-up": "\uebff", + "layout-sidebar-right-off": "\uec00", + "layout-panel-off": "\uec01", + "layout-sidebar-left-off": "\uec02", + "blank": "\uec03", + "heart-filled": "\uec04", + "map": "\uec05", + "map-filled": "\uec06", + "circle-small": "\uec07", + "bell-slash": "\uec08", + "bell-slash-dot": "\uec09", + "comment-unresolved": "\uec0a", + "git-pull-request-go-to-changes": "\uec0b", + "git-pull-request-new-changes": "\uec0c", +} + + +def get_codicon(name): + if name in codicons: + return codicons[name] diff --git a/cupcake/utils/colorizer.py b/cupcake/utils/colorizer.py new file mode 100644 index 0000000..e631c3a --- /dev/null +++ b/cupcake/utils/colorizer.py @@ -0,0 +1,11 @@ +import tkinter as tk + + +def colorize(text_widget, keyword, tag): + pos = 1.0 + while True: + idx = text_widget.search(keyword, pos, tk.END) + if not idx: + break + pos = '{}+{}c'.format(idx, len(keyword)) + text_widget.tag_add(tag, idx, pos) diff --git a/cupcake/utils/entry.py b/cupcake/utils/entry.py new file mode 100644 index 0000000..113374e --- /dev/null +++ b/cupcake/utils/entry.py @@ -0,0 +1,21 @@ +import tkinter as tk + +from hintedtext import HintedEntry +from .frame import Frame + + +class Entry(Frame): + def __init__(self, master, hint="", *args, **kwargs): + super().__init__(master) + self.config(padx=1, pady=1, bg=self.base.theme.border) + self.grid_columnconfigure(0, weight=1) + + self.entry = HintedEntry(self, relief=tk.FLAT, font=("Segoi UI", 10), **self.base.theme.utils.entry, bd=5, hint=hint) + self.entry.config(*args, **kwargs) + self.entry.grid(row=0, column=0, sticky=tk.NSEW) + + def insert(self, *args): + self.entry.insert(*args) + + def get(self, *args): + return self.entry.get(*args) \ No newline at end of file diff --git a/cupcake/utils/filetype.py b/cupcake/utils/filetype.py new file mode 100644 index 0000000..1c81ede --- /dev/null +++ b/cupcake/utils/filetype.py @@ -0,0 +1,11 @@ +import filetype + + +class FileType: + @staticmethod + def get_file_type(file_path): + return filetype.guess(file_path) + + @staticmethod + def is_image(file_path): + return filetype.is_image(file_path) diff --git a/cupcake/utils/frame.py b/cupcake/utils/frame.py new file mode 100644 index 0000000..f0c856a --- /dev/null +++ b/cupcake/utils/frame.py @@ -0,0 +1,11 @@ +import tkinter as tk + + +class Frame(tk.Frame): + """ + normal frame with reference to base + """ + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base diff --git a/cupcake/utils/icon.py b/cupcake/utils/icon.py new file mode 100644 index 0000000..a831fa8 --- /dev/null +++ b/cupcake/utils/icon.py @@ -0,0 +1,17 @@ +import tkinter as tk + +from .codicon import get_codicon +from .label import Label + + +class Icon(Label): + """ + Button with only an icon + """ + def __init__(self, master, icon, iconsize=14, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.icon = icon + self.config(text=get_codicon(icon), font=("codicon", iconsize)) + + def set_icon(self, icon): + self.config(text=get_codicon(icon)) diff --git a/cupcake/utils/iconbutton.py b/cupcake/utils/iconbutton.py new file mode 100644 index 0000000..bd6ef58 --- /dev/null +++ b/cupcake/utils/iconbutton.py @@ -0,0 +1,37 @@ +import tkinter as tk + +from .codicon import get_codicon + +from .menubutton import Menubutton + + +class IconButton(Menubutton): + """ + Button with only an icon + """ + def __init__(self, master, icon, event=lambda *_:..., icon2=None, iconsize=14, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.config(**self.base.theme.utils.iconbutton) + self.icons = [icon, icon2] + self.icon2 = icon2 + self.switch = False + + self.event = event + self.config(text=get_codicon(icon), font=("codicon", iconsize)) + + self.bind("", self.onclick) + + def onclick(self, *_): + self.event() + if not self.icon2: + return + + self.switch = not self.switch + self.config(text=get_codicon(self.icons[self.switch])) + self.v_onclick() + + def v_onclick(self): + ... + + def set_icon(self, icon): + self.config(text=get_codicon(icon)) diff --git a/cupcake/utils/iconlabelbutton.py b/cupcake/utils/iconlabelbutton.py new file mode 100644 index 0000000..50e1f13 --- /dev/null +++ b/cupcake/utils/iconlabelbutton.py @@ -0,0 +1,77 @@ +import tkinter as tk + +from .codicon import get_codicon +from .frame import Frame + + +class IconLabelButton(Frame): + def __init__(self, master, text=None, icon=None, function=lambda *_: None, padx=5, pady=1, *args, **kwargs): + super().__init__(master, padx=padx, pady=pady, *args, **kwargs) + self.function = function + + self.bg, self.fg, self.hbg, self.hfg = self.base.theme.utils.iconlabelbutton.values() + self.config(bg=self.bg) + self.text = text + self.icon = icon + + if icon: + self.icon_label = tk.Label(self, text=get_codicon(self.icon), anchor=tk.CENTER, + bg=self.bg, fg=self.fg, font=("codicon", 14)) + self.icon_label.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + if text: + self.text_label = tk.Label(self, text=self.text, anchor=tk.CENTER, pady=2, + bg=self.bg, fg=self.fg, font=("Segoe UI", 10)) + self.text_label.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + self.config_bindings() + self.visible = False + + def config_bindings(self): + self.bind("", self.on_enter) + self.bind("", self.on_leave) + + self.bind("", self.on_click) + if self.text: + self.text_label.bind("", self.on_click) + if self.icon: + self.icon_label.bind("", self.on_click) + + def on_enter(self, *_): + self.config(bg=self.hbg) + if self.text: + self.text_label.config(bg=self.hbg, fg=self.hfg) + if self.icon: + self.icon_label.config(bg=self.hbg, fg=self.hfg) + + def on_leave(self, *_): + self.config(bg=self.bg) + if self.text: + self.text_label.config(bg=self.bg, fg=self.fg) + if self.icon: + self.icon_label.config(bg=self.bg, fg=self.fg) + + def on_click(self, *_): + self.function() + + def change_text(self, text): + self.text_label.config(text=text) + + def change_icon(self, icon): + self.icon_label.config(text=icon) + + def set_pack_data(self, **kwargs): + self.pack_data = kwargs + + def get_pack_data(self): + return self.pack_data + + def show(self): + if not self.visible: + self.visible = True + self.pack(**self.get_pack_data()) + + def hide(self): + if self.visible: + self.visible = False + self.pack_forget() \ No newline at end of file diff --git a/cupcake/utils/label.py b/cupcake/utils/label.py new file mode 100644 index 0000000..d17ca52 --- /dev/null +++ b/cupcake/utils/label.py @@ -0,0 +1,48 @@ +import textwrap +import tkinter as tk + +from .frame import Frame + + +class Label(tk.Label): + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base + + +class WrappingLabel(Label): + """ + a type of Label that automatically adjusts the wrap to the size + """ + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.bind('', lambda e: self.config(wraplength=self.winfo_width())) + + +class TruncatedLabel(Frame): + """ + NOTE: Doesnt work currently + """ + def __init__(self, master, text, *args, **kwargs): + super().__init__(master) + self.text = text + self.pack_propagate(False) + + self.label = Label(self, text=text, *args, **kwargs) + self.label.pack(fill="both", expand=True) + + self.bind("", self._wrap_text) + + def _wrap_text(self, event=None): + width = self.winfo_width() * (len(self.text)/self.label.winfo_width()) + + if width > 0: + wrapped_text = textwrap.fill(self.text, width=width) + + if len(wrapped_text) > len(self.text): + # Truncate the text and add "..." at the end + truncated_text = textwrap.shorten(wrapped_text, width - 3, placeholder="...") + self.label.configure(text=truncated_text) + else: + self.label.configure(text=wrapped_text) diff --git a/cupcake/utils/menubutton.py b/cupcake/utils/menubutton.py new file mode 100644 index 0000000..44499f5 --- /dev/null +++ b/cupcake/utils/menubutton.py @@ -0,0 +1,11 @@ +import tkinter as tk + + +class Menubutton(tk.Menubutton): + """ + normal menubutton with reference to base + """ + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base diff --git a/cupcake/utils/scrollableframe.py b/cupcake/utils/scrollableframe.py new file mode 100644 index 0000000..8826482 --- /dev/null +++ b/cupcake/utils/scrollableframe.py @@ -0,0 +1,45 @@ +import tkinter as tk +from tkinter import ttk + +from .frame import Frame +from .canvas import Canvas + + +class Scrollbar(ttk.Scrollbar): + def set(self, low, high): + if float(low) <= 0.0 and float(high) >= 1.0: + self.pack_forget() + else: + self.pack(side=tk.RIGHT, fill=tk.Y) + ttk.Scrollbar.set(self, low, high) + + +class ScrollableFrame(Frame): + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.config(bg=self.base.theme.border) + + self.scrollbar = Scrollbar(self) + self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + self.canvas = Canvas(self, yscrollcommand=self.scrollbar.set, **self.base.theme.editors) + self.canvas.configure(highlightthickness=0) + self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + self.scrollbar.config(command=self.canvas.yview) + + self.content = Frame(self.canvas, **self.base.theme.editors) + self._content = self.canvas.create_window((0, 0), window=self.content, anchor="nw") + + self.content.bind("", self._scroll) + self.canvas.bind("", self._configure_canvas) + + def _scroll(self, event): + self.canvas.configure(scrollregion=self.canvas.bbox("all")) + + def _configure_canvas(self, event): + canvas_width = event.width + self.canvas.itemconfig(self._content, width=canvas_width) + + def add(self, content): + content.pack(in_=self.content) diff --git a/cupcake/utils/scrollbar.py b/cupcake/utils/scrollbar.py new file mode 100644 index 0000000..5a6bdf8 --- /dev/null +++ b/cupcake/utils/scrollbar.py @@ -0,0 +1,10 @@ +from tkinter import ttk + + +class Scrollbar(ttk.Scrollbar): + def set(self, low, high): + if float(low) <= 0.0 and float(high) >= 1.0: + self.grid_remove() + else: + self.grid() + ttk.Scrollbar.set(self, low, high) diff --git a/cupcake/utils/shortcut.py b/cupcake/utils/shortcut.py new file mode 100644 index 0000000..62a29da --- /dev/null +++ b/cupcake/utils/shortcut.py @@ -0,0 +1,24 @@ +import tkinter as tk + +from .frame import Frame + + +class Shortcut(Frame): + def __init__(self, master, shortcuts, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.shortcuts = shortcuts + self.add_shortcuts() + + def add_shortcuts(self): + for shortcut in self.shortcuts[:-1]: + self.add_shortcut(shortcut) + self.add_separator() + self.add_shortcut(self.shortcuts[-1]) + + def add_separator(self): + tk.Label(self, text="+", **self.base.theme.editors.labels).pack(padx=2, side=tk.LEFT) + + def add_shortcut(self, shortcut): + tk.Label( + self, text=shortcut, bg=self.base.theme.border, fg=self.base.theme.biscuit_dark, + font=("Consolas", 10)).pack(padx=2, side=tk.LEFT) \ No newline at end of file diff --git a/cupcake/utils/text.py b/cupcake/utils/text.py new file mode 100644 index 0000000..f650bff --- /dev/null +++ b/cupcake/utils/text.py @@ -0,0 +1,11 @@ +import tkinter as tk + + +class Text(tk.Text): + """ + normal text with reference to base + """ + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base diff --git a/cupcake/utils/toplevel.py b/cupcake/utils/toplevel.py new file mode 100644 index 0000000..5964627 --- /dev/null +++ b/cupcake/utils/toplevel.py @@ -0,0 +1,13 @@ +import tkinter as tk + + +class Toplevel(tk.Toplevel): + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master + self.base = master.base + + def geometry_size(self, width=None, height=None): + app_width = round(width * self.base.scale) + app_height = round(height * self.base.scale) + self.geometry(f"{app_width}x{app_height}") diff --git a/cupcake/utils/tree.py b/cupcake/utils/tree.py new file mode 100644 index 0000000..94b9faf --- /dev/null +++ b/cupcake/utils/tree.py @@ -0,0 +1,89 @@ +import tkinter.ttk as ttk +import tkinter as tk +from tkinter.constants import * + +from .scrollbar import Scrollbar +from .frame import Frame + + +class Tree(Frame): + def __init__(self, master, startpath=None, doubleclick=lambda _: None, singleclick=lambda _: None, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.config(**self.base.theme.utils.tree) + + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + + self.path = startpath + self.doubleclick = doubleclick + self.singleclick = singleclick + + self.tree = ttk.Treeview(self, show="tree", columns=("fullpath", "type"), + displaycolumns='', selectmode=tk.BROWSE) + self.tree.grid(row=0, column=0, sticky=NSEW) + + self.scrollbar = Scrollbar(self, orient=tk.VERTICAL, command=self.tree.yview, style="TreeScrollbar") + self.scrollbar.grid(row=0, column=1, sticky=tk.NS) + + self.tree.config(yscrollcommand=self.scrollbar.set) + self.scrollbar.config(command=self.tree.yview) + + self.tree.bind("", self.doubleclick) + self.tree.bind("<>", self.check_singleclick) + + def check_singleclick(self, _): + if self.item_type(self.focus()) == 'file': + if self.singleclick: + self.singleclick(self.item_fullpath(self.focus())) + else: + self.toggle_node(self.focus()) + + def clear_node(self, node): + self.tree.delete(*self.tree.get_children(node)) + + def clear_tree(self): + self.clear_node('') + + def collapse_all(self): + for node in self.tree.get_children(): + self.tree.item(node, open=False) + + def delete(self, *a, **kw): + self.tree.delete(*a, *kw) + + def focus(self): + return self.tree.focus() + + def get_children(self, *a, **kw): + return self.tree.get_children(*a, **kw) + + def insert(self, *args, **kwargs): + return self.tree.insert(*args, **kwargs) + + def is_open(self, node): + return self.tree.item(node, "open") + + def item(self, *a, **kw): + return self.tree.item(*a, **kw) + + def item_type(self, item): + return self.set(item, "type") + + def item_fullpath(self, item): + return self.set(item, "fullpath") + + def selected_path(self): + return self.item_fullpath(self.focus()) + + def selected_type(self): + return self.item_type(self.focus()) + + def set(self, *args, **kwargs): + return self.tree.set(*args, **kwargs) + + def toggle_node(self, node): + if self.item_type(node) == 'directory': + if self.is_open(node): + self.tree.item(node, open=False) + else: + self.tree.item(node, open=True)