From 0f67d8527b9023d8904596c51326ebd6d6ee8c51 Mon Sep 17 00:00:00 2001 From: Billy Date: Fri, 21 Jul 2023 01:32:55 +0800 Subject: [PATCH] revert: Clean unclean commit from main branch --- 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 ++- .../autocomplete/{kinds.py => itemkinds.py} | 46 +- cupcake/editor/autocomplete/kind.py | 41 -- 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, 1567 insertions(+), 1751 deletions(-) create mode 100644 cupcake/config/languages/cpp.py create mode 100644 cupcake/config/languages/language.py rename cupcake/editor/autocomplete/{kinds.py => itemkinds.py} (70%) delete mode 100644 cupcake/editor/autocomplete/kind.py create mode 100644 cupcake/editor/events.py create mode 100644 cupcake/editor/find_replace/__init__.py create mode 100644 cupcake/editor/find_replace/button.py create mode 100644 cupcake/editor/find_replace/container.py create mode 100644 cupcake/editor/find_replace/entrybox.py create mode 100644 cupcake/editor/find_replace/find_replace.py create mode 100644 cupcake/editor/find_replace/findbox.py create mode 100644 cupcake/editor/find_replace/replacebox.py create mode 100644 cupcake/editor/find_replace/results.py create mode 100644 cupcake/editor/find_replace/toggle.py create mode 100644 cupcake/editor/find_replace/togglew.py create mode 100644 cupcake/editor/language/__init__.py create mode 100644 cupcake/editor/language/languages/__init__.py create mode 100644 cupcake/editor/language/languages/cpp.py create mode 100644 cupcake/editor/language/syntax.py create mode 100644 cupcake/editor/language/test.py create mode 100644 cupcake/editor/scrollbar.py delete mode 100644 cupcake/editor/syntax.py create mode 100644 cupcake/editor/textw.py delete mode 100644 cupcake/utils/__init__.py delete mode 100644 cupcake/utils/bubble.py delete mode 100644 cupcake/utils/button.py delete mode 100644 cupcake/utils/buttonsentry.py delete mode 100644 cupcake/utils/canvas.py delete mode 100644 cupcake/utils/codicon.py delete mode 100644 cupcake/utils/colorizer.py delete mode 100644 cupcake/utils/entry.py delete mode 100644 cupcake/utils/filetype.py delete mode 100644 cupcake/utils/frame.py delete mode 100644 cupcake/utils/icon.py delete mode 100644 cupcake/utils/iconbutton.py delete mode 100644 cupcake/utils/iconlabelbutton.py delete mode 100644 cupcake/utils/label.py delete mode 100644 cupcake/utils/menubutton.py delete mode 100644 cupcake/utils/scrollableframe.py delete mode 100644 cupcake/utils/scrollbar.py delete mode 100644 cupcake/utils/shortcut.py delete mode 100644 cupcake/utils/text.py delete mode 100644 cupcake/utils/toplevel.py delete mode 100644 cupcake/utils/tree.py diff --git a/cupcake/__init__.py b/cupcake/__init__.py index 397905d..e9c49d9 100644 --- a/cupcake/__init__.py +++ b/cupcake/__init__.py @@ -1,10 +1,3 @@ -__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__), '.'))) - +__version__ = '0.5.1' from .editor import Editor diff --git a/cupcake/config/languages/cpp.py b/cupcake/config/languages/cpp.py new file mode 100644 index 0000000..31271c1 --- /dev/null +++ b/cupcake/config/languages/cpp.py @@ -0,0 +1,188 @@ +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 new file mode 100644 index 0000000..70bd280 --- /dev/null +++ b/cupcake/config/languages/language.py @@ -0,0 +1,5 @@ +class Language: + keywords: list + strings: list + numbers: list + comments: list diff --git a/cupcake/editor/__init__.py b/cupcake/editor/__init__.py index 2753377..1b09f1e 100644 --- a/cupcake/editor/__init__.py +++ b/cupcake/editor/__init__.py @@ -1,78 +1,81 @@ import tkinter as tk -from ...utils import Scrollbar -from ..editor import BaseEditor - -from .minimap import Minimap +from ..config import Config +from .events import Events from .linenumbers import LineNumbers +from .minimap import Minimap +from .scrollbar import Scrollbar +from .language import SyntaxLoader from .text import Text +from .find_replace import FinderReplacer -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) +class Editor(tk.Frame): + def __init__(self, master, *args, **kwargs): + super().__init__(master, *args, **kwargs) + self.master = master - 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.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) + + self.linenumebers.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.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() + self.events = Events(self) + self.text.config(yscrollcommand=self.text_scrolled) + self.focus() - def on_scroll(self, *_): - self.linenumbers.redraw() - if not self.minimalist: - self.minimap.redraw() + def text_scrolled(self, *args): + pass - 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 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 focus(self): self.text.focus() - self.on_change() + self.refresh_editor() def set_fontsize(self, size): self.font.configure(size=size) - self.linenumbers.set_bar_width(size * 3) - self.on_change() - - def save(self, path=None): - if self.editable: - self.text.save_file(path) - - def cut(self, *_): - if self.editable: - self.text.cut() - - def copy(self, *_): - if self.editable: - self.text.copy() + 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 paste(self, *_): - if self.editable: - self.text.paste() + def insert(self, text): + self.text.clear_insert(text) + + def load_file(self, filepath): + self.text.load_file(filepath) diff --git a/cupcake/editor/autocomplete/__init__.py b/cupcake/editor/autocomplete/__init__.py index 65ddd04..7eeba89 100644 --- a/cupcake/editor/autocomplete/__init__.py +++ b/cupcake/editor/autocomplete/__init__.py @@ -1,36 +1,43 @@ import tkinter as tk -from itertools import chain -from .kinds import Kinds +from .itemkinds import Kinds from .item import AutoCompleteItem -from core.components.utils import Toplevel - -class AutoComplete(Toplevel): +class AutoComplete(tk.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(padx=1, pady=1, bg=self.base.theme.border) + self.config(bg="#454545", padx=1, pady=1) 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() @@ -38,28 +45,24 @@ def update_completions(self): term = self.master.get_current_word() - 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)) + 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] 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, *_): + def move_up(self, *args): if self.active: self.select(-1) return "break" - def move_down(self, *_): + def move_down(self, *args): if self.active: self.select(1) return "break" @@ -72,16 +75,22 @@ def add_all_items(self): self.refresh_selected() def update_all_words(self): - for word in self.master.words: + for word in self.master.get_all_words(): if word not in self.get_items_text(): self.add_item(word, "word") - for word in self.menu_items: - if word.get_text() not in self.master.words and word.get_kind() == "word": + for word in self.get_items(): + if word.get_text() not in self.master.get_all_words() and word.get_kind() == "word": self.remove_item(word) - def add_item(self, text, kind=None): - new_item = AutoCompleteItem(self, text, kind=kind) + 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) new_item.grid(row=self.row, sticky=tk.EW) self.menu_items.append(new_item) @@ -111,6 +120,9 @@ 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] @@ -132,7 +144,7 @@ def show_items(self, items, term): self.reset_selection() - def refresh_geometry(self, *_): + def refresh_geometry(self, *args): self.update_idletasks() self.geometry("+{}+{}".format(*self.master.cursor_screen_location())) @@ -142,7 +154,7 @@ def show(self, pos): self.geometry("+{}+{}".format(*pos)) self.deiconify() - def hide(self, *_): + def hide(self, *args): self.active = False self.withdraw() self.reset() @@ -150,10 +162,10 @@ def hide(self, *_): def reset(self): self.reset_selection() - def choose(self, this=None, *_): + def choose(self, this=None, *args): + self.hide() 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 069f994..e361049 100644 --- a/cupcake/editor/autocomplete/item.py +++ b/cupcake/editor/autocomplete/item.py @@ -1,28 +1,67 @@ +from cgitb import text import tkinter as tk -from .kind import Kind -from core.components.utils import Frame +from .itemkinds import Kinds -class AutoCompleteItem(Frame): - def __init__(self, master, text, kind=None, *args, **kwargs): + +class Kind(tk.Label): + def __init__(self, master, kinds, kind="text", *args, **kwargs): super().__init__(master, *args, **kwargs) - 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.master = master + self.kinds = kinds + self.kind = kind + + self.image = None - self.text = text + 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.kind = kind self.kindw = Kind(self, self.master.autocomplete_kinds, kind) - 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 = 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.tag_config("term", foreground=self.base.theme.biscuit) + self.leftw.tag_config("term", foreground="#18a3ff") + self.config(bg="#1e1e1e", width=300) + self.kindw.bind("", self.on_click) - self.textw.bind("", self.on_click) + self.leftw.bind("", self.on_click) self.bind("", self.on_hover) self.bind("", self.off_hover) @@ -34,33 +73,33 @@ def __init__(self, master, text, kind=None, *args, **kwargs): self.grid_rowconfigure(0, weight=1) self.kindw.grid(row=0, column=0, sticky=tk.NSEW) - self.textw.grid(row=0, column=1, sticky=tk.NSEW) + self.leftw.grid(row=0, column=1, sticky=tk.NSEW) def get_text(self): - return self.text + return self.left def get_kind(self): return self.kind def mark_term(self, term): - start_pos = self.text.find(term) + start_pos = self.left.find(term) end_pos = start_pos + len(term) - self.textw.tag_remove("term", 1.0, tk.END) - self.textw.tag_add("term", f"1.{start_pos}", f"1.{end_pos}") + self.leftw.tag_remove("term", 1.0, tk.END) + self.leftw.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=self.hbg) - self.textw.config(bg=self.hbg) + self.kindw.config(bg="#2a2d2e") + self.leftw.config(bg="#2a2d2e") self.hovered = True def off_hover(self, *args): if not self.selected: - self.kindw.config(bg=self.bg) - self.textw.config(bg=self.bg) + self.kindw.config(bg="#252526") + self.leftw.config(bg="#252526") self.hovered = False def toggle_selection(self): @@ -70,11 +109,11 @@ def toggle_selection(self): self.deselect() def select(self): - self.kindw.config(bg=self.hbg) - self.textw.config(bg=self.hbg, fg=self.hfg) + self.kindw.config(bg="#094771") + self.leftw.config(bg="#094771", fg="#ffffff") self.selected = True def deselect(self): - self.kindw.config(bg=self.bg) - self.textw.config(bg=self.bg, fg=self.fg) + self.kindw.config(bg="#252526") + self.leftw.config(bg="#252526", fg="#d4d4d4") self.selected = False \ No newline at end of file diff --git a/cupcake/editor/autocomplete/kinds.py b/cupcake/editor/autocomplete/itemkinds.py similarity index 70% rename from cupcake/editor/autocomplete/kinds.py rename to cupcake/editor/autocomplete/itemkinds.py index f4a0b43..2504849 100644 --- a/cupcake/editor/autocomplete/kinds.py +++ b/cupcake/editor/autocomplete/itemkinds.py @@ -52,26 +52,26 @@ def __init__(self, master, *args, **kwargs): 2WtJ9TiOf6yZfDiEMDaz7SiKPhiApM9A2d0bGyY3gPJ8Pv8CS1/l7mEDXsgNYDwefwP2KpXKmXW4yPcajcbXvzh Jkn1JO+7+JMuyq6tgr9e75u5PJe00m82fC7Mnk8lt4FUI4fEqLOkR8LLoAfALta2R2TiY6xMAAAAASUVORK5CYI I=""") - 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=""") + 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=""") diff --git a/cupcake/editor/autocomplete/kind.py b/cupcake/editor/autocomplete/kind.py deleted file mode 100644 index 0fe8320..0000000 --- a/cupcake/editor/autocomplete/kind.py +++ /dev/null @@ -1,41 +0,0 @@ -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/events.py b/cupcake/editor/events.py new file mode 100644 index 0000000..626d77c --- /dev/null +++ b/cupcake/editor/events.py @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..0af3ec4 --- /dev/null +++ b/cupcake/editor/find_replace/__init__.py @@ -0,0 +1,68 @@ +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 new file mode 100644 index 0000000..a61ed19 --- /dev/null +++ b/cupcake/editor/find_replace/button.py @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000..468e769 --- /dev/null +++ b/cupcake/editor/find_replace/container.py @@ -0,0 +1,68 @@ +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 new file mode 100644 index 0000000..e08ecc8 --- /dev/null +++ b/cupcake/editor/find_replace/entrybox.py @@ -0,0 +1,39 @@ +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 new file mode 100644 index 0000000..b3901ec --- /dev/null +++ b/cupcake/editor/find_replace/find_replace.py @@ -0,0 +1,226 @@ +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 new file mode 100644 index 0000000..822a720 --- /dev/null +++ b/cupcake/editor/find_replace/findbox.py @@ -0,0 +1,34 @@ +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 new file mode 100644 index 0000000..f88565c --- /dev/null +++ b/cupcake/editor/find_replace/replacebox.py @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..19db7e6 --- /dev/null +++ b/cupcake/editor/find_replace/results.py @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..0763ed3 --- /dev/null +++ b/cupcake/editor/find_replace/toggle.py @@ -0,0 +1,52 @@ +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 new file mode 100644 index 0000000..78f6f67 --- /dev/null +++ b/cupcake/editor/find_replace/togglew.py @@ -0,0 +1,61 @@ +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 6378bca..53ecba1 100644 --- a/cupcake/editor/highlighter.py +++ b/cupcake/editor/highlighter.py @@ -1,50 +1,43 @@ -import os, tkinter as tk -from pygments import lex -from pygments.lexers import get_lexer_for_filename -from pygments.util import ClassNotFound +import tkinter as tk class Highlighter: def __init__(self, master, *args, **kwargs): self.text = master - self.base = master.base - 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.syntax = master.master.syntax self.setup_highlight_tags() def setup_highlight_tags(self): - for token, color in self.tag_colors.items(): - self.text.tag_configure(str(token), foreground=color) + 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") - def highlight(self): - if not self.lexer: - return + def highlight_all(self): + self.highlight_pattern(self.syntax.rgx_keywords, "keywords", regexp=True) + self.highlight_pattern(self.syntax.rgx_numbers, "numbers", 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("==================================") + self.highlight_pattern(self.syntax.rgx_strings, "strings", regexp=True) + self.highlight_pattern(self.syntax.rgx_comments, "comments", regexp=True) diff --git a/cupcake/editor/language/__init__.py b/cupcake/editor/language/__init__.py new file mode 100644 index 0000000..76f74ec --- /dev/null +++ b/cupcake/editor/language/__init__.py @@ -0,0 +1 @@ +from .syntax import SyntaxLoader diff --git a/cupcake/editor/language/languages/__init__.py b/cupcake/editor/language/languages/__init__.py new file mode 100644 index 0000000..9514657 --- /dev/null +++ b/cupcake/editor/language/languages/__init__.py @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..c095434 --- /dev/null +++ b/cupcake/editor/language/languages/cpp.py @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..2c41b2d --- /dev/null +++ b/cupcake/editor/language/syntax.py @@ -0,0 +1,24 @@ +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 new file mode 100644 index 0000000..c800483 --- /dev/null +++ b/cupcake/editor/language/test.py @@ -0,0 +1,44 @@ +""" +[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 0d066b5..115e636 100644 --- a/cupcake/editor/linenumbers/__init__.py +++ b/cupcake/editor/linenumbers/__init__.py @@ -2,64 +2,89 @@ from .breakpoint import Breakpoint -from core.components.utils import Canvas, Menubutton - -class LineNumbers(Canvas): - def __init__(self, master, text=None, *args, **kwargs): +class LineNumbers(tk.Frame): + def __init__(self, master, text, *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.master = master + + self.config_appearance() + 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.dlineinfo(line) + dline = self.text.get_line_info(line) if not dline: return y = dline[1] - 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) + 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 set_bar_width(self, width): - self.configure(width=width) - - def redraw(self, *_): - self.delete(tk.ALL) + def highlight_current_line(self): + self.mark_line(tk.INSERT) + + def select_line(self, line): + self.text.select_line(line) - i = self.text.index("@0,0") - while True : - dline = self.text.dlineinfo(i) - if dline is None: + def redraw(self, *args): + self.clear() + self.highlight_current_line() + self.redraw_line_numbers() + + def redraw_line_numbers(self): + i = self.text.get_origin() + while True: + dline = self.text.get_line_info(i) + if not dline: break y = dline[1] - linenum = str(i).split(".")[0] + ln = str(i).split(".")[0] - # to highlight current line - curline = self.text.dlineinfo(tk.INSERT) - cur_y = curline[1] if curline else None + curline = self.text.get_line_info(tk.INSERT) + cur_y = None + if curline: + cur_y = curline[1] if y == cur_y: - self.create_text(40, y, anchor=tk.NE, text=linenum, font=self.font, - fill=self.base.theme.editors.linenumbers.number.highlightforeground, tag=i) + number = self.cw.create_text(46, y, anchor=tk.NE, text=ln, font=self.font, fill=self.highlight_fill, tag=i) else: - self.create_text(40, y, anchor=tk.NE, text=linenum, font=self.font, - fill=self.base.theme.editors.linenumbers.number.foreground, tag=i) + number = self.cw.create_text(46, y, anchor=tk.NE, text=ln, font=self.font, fill=self.fill, tag=i) - self.tag_bind(i, "", lambda _, i=i: self.text.select_line(i)) + self.cw.tag_bind(i, "", lambda _, i=i: self.select_line(i)) - # TODO drawing breakpoints - need optimisations + # drawing breakpoints - needs optimisations # self.draw_breakpoint(y) i = self.text.index(f"{i}+1line") - + def draw_breakpoint(self, y): - bp = Breakpoint(self) - self.create_window(21, y-2, anchor=tk.NE, window=bp) + bp = Breakpoint(self.cw) + self.cw.create_window(21, y-2, anchor=tk.NE, window=bp) + + def toggle_breakpoint(self, y): + ... diff --git a/cupcake/editor/linenumbers/breakpoint.py b/cupcake/editor/linenumbers/breakpoint.py index 1c0c5ca..537b48b 100644 --- a/cupcake/editor/linenumbers/breakpoint.py +++ b/cupcake/editor/linenumbers/breakpoint.py @@ -5,9 +5,8 @@ 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), fg="#1e1e1e", cursor="hand2", **self.base.theme.editors.linenumbers, + self.config(text="●", font=("Consolas", 14), bg="#1e1e1e", fg="#1e1e1e", cursor="hand2", borderwidth=0, width=2, height=1, pady=0, padx=0, relief=tk.FLAT) self.active = False @@ -16,6 +15,7 @@ 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,6 +29,7 @@ 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 3f79518..2f50ca8 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 -#TODO update minimap when scrollbar is used -class Minimap(Frame): +class Minimap(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(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.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.slider_image = tk.PhotoImage(data="""iVBORw0KGgoAAAANSUhEUgAAAG4AAABFCAYAAACrMNMO AAAACXBIWXMAAABfAAAAXwEqnu0dAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMBJRE @@ -38,14 +38,13 @@ def attach(self, textw): def redraw(self): self.cw.delete("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") + 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") y = int(self.tw.index(tk.INSERT).split(".")[0]) * 2 - self.cw.create_line(0, y, 100, y, fill="#dc8c34", width=2, tag="redrawn") + self.cw.create_line(0, y, 100, y, fill="#22374b", 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] @@ -70,5 +69,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() * 350)) - self.tw.master.on_scroll() + self.tw.yview(int(y / self.cw.winfo_height() * 100)) + self.tw.master.redraw_ln() diff --git a/cupcake/editor/scrollbar.py b/cupcake/editor/scrollbar.py new file mode 100644 index 0000000..f7d62c2 --- /dev/null +++ b/cupcake/editor/scrollbar.py @@ -0,0 +1,67 @@ +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 deleted file mode 100644 index 74503a3..0000000 --- a/cupcake/editor/syntax.py +++ /dev/null @@ -1,9 +0,0 @@ -#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 d51589b..dfa65c9 100644 --- a/cupcake/editor/text.py +++ b/cupcake/editor/text.py @@ -1,175 +1,159 @@ -import re, codecs, io -import threading +import re 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 -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.font = self.master.font + self.syntax = self.master.syntax + self.pack_propagate(False) + + self.keywords = self.syntax.keywords self.current_word = None self.words = [] - if self.exists: - self.load_file() + self.highlighter = Highlighter(self) + + self.current_indentation = None + self.current_line = None - self.syntax = Syntax(self) self.auto_completion = AutoComplete( - self, items=self.syntax.get_autocomplete_list()) if not minimalist else None - self.highlighter = Highlighter(self) + self, items=self.syntax.get_autocomplete_list()) - self.focus_set() - self.config_tags() self.create_proxy() + self.config_appearance() + self.config_tags() self.config_bindings() - self.configure(wrap=tk.NONE, relief=tk.FLAT, **self.base.theme.editors.text) + + 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.update_words() + 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") - 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") + return result + + 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_bindings(self): - self.bind("", self.key_release_events) + self.bind("", self.key_release_events) - self.bind("", self.open_find_replace) + self.bind("", self.select_all) 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.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") - + self.bind("", + lambda e: self.handle_ctrl_hmovement()) + self.bind("", + lambda e: self.handle_ctrl_hmovement(True)) + def key_release_events(self, event): - if event.keysym not in ("Up", "Down", "Return"): - self.show_autocomplete(event) + self.show_autocomplete(event) + self.update_current_line() 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": - return self.complete_pair("}") + self.complete_bracket("}") case "bracketleft": - return self.complete_pair("]") + self.complete_bracket("]") case "parenleft": - return self.complete_pair(")") + self.complete_bracket(")") # surroundings for selection case "apostrophe": - return self.surrounding_selection("\'") + self.surrounding_selection("\'") case "quotedbl": - return self.surrounding_selection("\"") + self.surrounding_selection("\"") + + # autocompletion keys + case "Button-2" | "BackSpace" | "Escape" | "Control_L" | "Control_R" | "space": + self.hide_autocomplete() + case "rightarrow" | "leftarrow": + self.update_completions() + # key events + case "Return": + self.enter_key_events() + case "Tab": + self.tab_key_events() + # extra spaces case ":" | ",": self.insert(tk.INSERT, " ") case _: pass - - def enter_key_events(self, *_): - if not self.minimalist and self.auto_completion.active: + + 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: 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" - - self.insert(tk.INSERT, " "*4) + + 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" - - def get_all_text(self, *args): - return self.get(1.0, tk.END) - 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_all_text(self): + return self.get_all_text() + + def get_all_words(self): + return self.get_all_words() + 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 @@ -180,17 +164,11 @@ 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 hide_autocomplete(self, *_): - if self.minimalist: - return - - self.auto_completion.hide() - def show_autocomplete(self, event): - if self.minimalist or not self.check_autocomplete_keys(event): + if not self.check_autocomplete_keys(event): return - - if self.current_word.strip() not in ["{", "}", ":", "", None, "\""] and not self.current_word.strip()[0].isdigit(): + + if self.current_word.strip() not in ["{", "}", ":", "", None, "\""]: if not self.auto_completion.active: if event.keysym in ["Left", "Right"]: return @@ -202,40 +180,37 @@ def show_autocomplete(self, event): else: if self.auto_completion.active: self.hide_autocomplete() - - def complete_pair(self, char): - self.insert(tk.INSERT, char) - self.mark_set(tk.INSERT, "insert-1c") - 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" - - if self.tag_ranges(tk.SEL): - self.insert(char, tk.SEL_LAST) - self.insert(char, tk.SEL_FIRST) - return - - self.complete_pair(char) - return "break" - - def move_to_next_word(self): - self.mark_set(tk.INSERT, self.index("insert+1c wordend")) + 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 move_to_previous_word(self): - self.mark_set(tk.INSERT, self.index("insert-1c wordstart")) + 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 move_cursor(self, position): + self.mark_set(tk.INSERT, position) - def handle_ctrl_hmovement(self, delta=False): - if delta: - self.move_to_next_word() - else: - self.move_to_previous_word() + def clear_all_selection(self): + self.tag_remove(tk.SEL, 1.0, tk.END) + + def select_line(self, line): + self.clear_all_selection() - return "break" + 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 update_current_indent(self): line = self.get("insert linestart", "insert lineend") match = re.match(r'^(\s+)', line) @@ -248,6 +223,9 @@ 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(): @@ -263,254 +241,77 @@ def check_indentation(self, *args): 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 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 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 get_origin(self): + return self.index("@0,0") - def cut(self, *_): - self.event_generate("<>") + def get_line_info(self, line): + return self.dlineinfo(line) - 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): + def clear_insert(self, content): self.delete(1.0, tk.END) + self.insert(1.0, content) - 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 load_file(self, path): + with open(path, 'r') as fp: + self.clear_insert(fp.read()) - def add_newline(self, count=1): - self.insert(tk.INSERT, "\n" * count) - - 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 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 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) + def select_all(self, *args): + self.tag_remove("highlight", 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) + self.tag_add(tk.SEL, 1.0, tk.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) + # scroll to top + # self.mark_set(tk.INSERT, 1.0) + # self.see(tk.INSERT) - self.move_cursor(end) - - def highlight_current_word(self): - if self.minimalist: - return + return "break" - self.tag_remove("highlight", 1.0, tk.END) - 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) + # def handle_space(self, *args): + # self.insert(tk.INSERT, "-") - 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 + # return "break" - self.mark_set("matchStart", index) - self.mark_set("matchEnd", f"{index}+{count.get()}c") + def multi_selection(self, *args): + #TODO: multi cursor editing - self.tag_add(tag, "matchStart", "matchEnd") + return "break" - def refresh(self, *args): - if self.minimalist: - return - - self.current_word = self.get("insert-1c wordstart", "insert") - self.highlighter.highlight() - self.highlight_current_word() - self.highlight_current_line() + 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 create_proxy(self): - self._orig = self._w + "_orig" - self.tk.call("rename", self._w, self._orig) - self.tk.createcommand(self._w, self._proxy) + 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 _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 + def update_words(self): + self.words = re.findall(r"\w+", self.get_all_text_ac()) + self.update_completion_words() - cmd = (self._orig,) + args - result = self.tk.call(cmd) + 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.highlighter.highlight_pattern(f"\\y{word[0]}\\y", "highlight", regexp=True) - 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 + def on_change(self, *args): + self.current_word = self.get("insert-1c wordstart", "insert") + self.update_words() + 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") diff --git a/cupcake/editor/textw.py b/cupcake/editor/textw.py new file mode 100644 index 0000000..a17d493 --- /dev/null +++ b/cupcake/editor/textw.py @@ -0,0 +1,86 @@ +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 deleted file mode 100644 index 375e18c..0000000 --- a/cupcake/utils/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -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 deleted file mode 100644 index bfd04b5..0000000 --- a/cupcake/utils/bubble.py +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index 65b1f2e..0000000 --- a/cupcake/utils/button.py +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 115cba8..0000000 --- a/cupcake/utils/buttonsentry.py +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index ee7418b..0000000 --- a/cupcake/utils/canvas.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 72d2b9b..0000000 --- a/cupcake/utils/codicon.py +++ /dev/null @@ -1,525 +0,0 @@ -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 deleted file mode 100644 index e631c3a..0000000 --- a/cupcake/utils/colorizer.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 113374e..0000000 --- a/cupcake/utils/entry.py +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 1c81ede..0000000 --- a/cupcake/utils/filetype.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index f0c856a..0000000 --- a/cupcake/utils/frame.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index a831fa8..0000000 --- a/cupcake/utils/icon.py +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index bd6ef58..0000000 --- a/cupcake/utils/iconbutton.py +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 50e1f13..0000000 --- a/cupcake/utils/iconlabelbutton.py +++ /dev/null @@ -1,77 +0,0 @@ -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 deleted file mode 100644 index d17ca52..0000000 --- a/cupcake/utils/label.py +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index 44499f5..0000000 --- a/cupcake/utils/menubutton.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 8826482..0000000 --- a/cupcake/utils/scrollableframe.py +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index 5a6bdf8..0000000 --- a/cupcake/utils/scrollbar.py +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 62a29da..0000000 --- a/cupcake/utils/shortcut.py +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index f650bff..0000000 --- a/cupcake/utils/text.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 5964627..0000000 --- a/cupcake/utils/toplevel.py +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 94b9faf..0000000 --- a/cupcake/utils/tree.py +++ /dev/null @@ -1,89 +0,0 @@ -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)