working on multiple editor types
This commit is contained in:
@@ -14,4 +14,5 @@ theme_mappings = {
|
||||
"yaml": "yaml",
|
||||
"md": "markdown",
|
||||
"gitignore": "markdown",
|
||||
"lua": "lua"
|
||||
}
|
||||
@@ -9,13 +9,18 @@ import os, shutil
|
||||
|
||||
|
||||
class CustomDirectoryTree(DirectoryTree):
|
||||
|
||||
ICON_NODE = " "
|
||||
ICON_NODE_EXPANDED = " "
|
||||
ICON_FILE = " "
|
||||
|
||||
def __init__(self, path, *, name = None, id = None, classes = None, disabled = False):
|
||||
super().__init__(path, name=name, id=id, classes=classes, disabled=disabled)
|
||||
self.right_clicked_node: TreeNode | None = None
|
||||
|
||||
|
||||
|
||||
def context_menu_chosen(self, result):
|
||||
def context_menu_chosen(self, file_name: str, result: str):
|
||||
if result == "Delete":
|
||||
def delete_confirm(will_delete: bool | None):
|
||||
if will_delete == True:
|
||||
@@ -44,7 +49,7 @@ class CustomDirectoryTree(DirectoryTree):
|
||||
self.right_clicked_node = None
|
||||
|
||||
self.app.push_screen(Prompt(f"Enter the new name for \"{self.right_clicked_node.label}\".", "string", "Rename"), rename_confirm)
|
||||
elif result == "New Folder":
|
||||
elif result == "New folder":
|
||||
def new_folder(folder_name: str | None):
|
||||
if folder_name == None: return
|
||||
if folder_name.strip() == "":
|
||||
@@ -60,7 +65,7 @@ class CustomDirectoryTree(DirectoryTree):
|
||||
return
|
||||
|
||||
self.app.push_screen(Prompt("Enter the name of the new folder.", "string", "Create New Folder"), new_folder)
|
||||
elif result == "New File":
|
||||
elif result == "New file":
|
||||
def new_file(file_name: str | None):
|
||||
if file_name == None: return
|
||||
if file_name.strip() == "":
|
||||
@@ -82,6 +87,24 @@ class CustomDirectoryTree(DirectoryTree):
|
||||
return
|
||||
|
||||
self.app.push_screen(Prompt("Enter the name of the new file", "string", "Create New File"), new_file)
|
||||
elif result == "Open with...":
|
||||
file_extension = file_name.rsplit(".")[1]
|
||||
default_editors = eval(self.app.config_handler.get("editor", "default_editors"))
|
||||
default_editor = default_editors.get(file_extension, "Text Editor")
|
||||
|
||||
def open_with(chosen_editor):
|
||||
self.notify(str(chosen_editor))
|
||||
|
||||
self.app.push_screen(Prompt(
|
||||
"Editor for file:",
|
||||
"list",
|
||||
"Choose editor for file",
|
||||
values=[
|
||||
default_editor + " (Default)",
|
||||
"Text Editor"
|
||||
],
|
||||
allow_blank=False
|
||||
), open_with)
|
||||
|
||||
def on_mouse_down(self, event: MouseDown):
|
||||
if event.button != 3 or not "line" in event.style.meta:
|
||||
@@ -89,16 +112,18 @@ class CustomDirectoryTree(DirectoryTree):
|
||||
selected_node = self.get_node_at_line(event.style.meta["line"])
|
||||
self.right_clicked_node = selected_node
|
||||
|
||||
spacer = NoSelectStatic(f'[d]{"-" * 17}[/]')
|
||||
|
||||
options = None
|
||||
if self._safe_is_dir(self.right_clicked_node.data.path):
|
||||
options = ["New Folder", "New File", NoSelectStatic(f'[d]{"-" * 17}[/]'), "Delete", "Rename"]
|
||||
options = ["New folder", "New file", spacer, "Delete", "Rename"]
|
||||
else:
|
||||
options = ["Delete", "Rename"]
|
||||
options = ["Delete", "Rename", spacer, "Open with..."]
|
||||
|
||||
file_name = str(self.right_clicked_node.label) if len(self.right_clicked_node.label) <= 17 else self.right_clicked_node.label[:14] + "..."
|
||||
|
||||
self.app.push_screen(ContextMenu(
|
||||
[NoSelectStatic(f"[b]{file_name}[/]"), NoSelectStatic(f'[d]{"-" * 17}[/]')] + options,
|
||||
event.screen_offset
|
||||
), self.context_menu_chosen)
|
||||
), lambda result: self.context_menu_chosen(file_name, result))
|
||||
|
||||
|
||||
585
main.py
585
main.py
@@ -21,341 +21,344 @@ import os, sys
|
||||
|
||||
|
||||
class Watcher(FileSystemEventHandler):
|
||||
def __init__(self, app: App):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
def __init__(self, app: App):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
async def on_any_event(self, event):
|
||||
await self.app.query_one(DirectoryTree).reload()
|
||||
async def on_any_event(self, event):
|
||||
await self.app.query_one(DirectoryTree).reload()
|
||||
|
||||
class Berry(App):
|
||||
CSS_PATH = "assets/style.tcss"
|
||||
SUB_TITLE = "New File"
|
||||
CSS_PATH = "assets/style.tcss"
|
||||
SUB_TITLE = "New File"
|
||||
|
||||
BINDINGS = [
|
||||
Binding("ctrl+o", "open", "Open File"),
|
||||
Binding("ctrl+n", "new", "New File"),
|
||||
Binding("ctrl+s", "save", "Save"),
|
||||
Binding("ctrl+shift+s", "save_as", "Save As...", priority=True),
|
||||
Binding("ctrl+f", "find", "Find", priority=True),
|
||||
Binding("ctrl+f1", "settings", "Settings")
|
||||
]
|
||||
BINDINGS = [
|
||||
Binding("ctrl+o", "open", "Open File"),
|
||||
Binding("ctrl+n", "new", "New File"),
|
||||
Binding("ctrl+s", "save", "Save"),
|
||||
Binding("ctrl+shift+s", "save_as", "Save As...", priority=True),
|
||||
Binding("ctrl+f", "find", "Find", priority=True),
|
||||
Binding("ctrl+f1", "settings", "Settings")
|
||||
]
|
||||
|
||||
def __init__(self, path: str):
|
||||
super().__init__()
|
||||
self.path = path
|
||||
self.config_handler = ConfigHandler(self)
|
||||
def __init__(self, path: str):
|
||||
super().__init__()
|
||||
self.path = path
|
||||
self.config_handler = ConfigHandler(self)
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
with Vertical(id="sidebar"):
|
||||
with HorizontalGroup(id="sidebar-buttons"):
|
||||
yield Button("📂")
|
||||
yield Button("🔍")
|
||||
|
||||
with ContentSwitcher(initial="files", id="sidebar-switcher"):
|
||||
with Vertical(id="files"):
|
||||
yield Static("EXPLORER")
|
||||
yield CustomDirectoryTree(self.path, id="directory")
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
with Vertical(id="sidebar"):
|
||||
with HorizontalGroup(id="sidebar-buttons"):
|
||||
yield Button("")
|
||||
yield Button("")
|
||||
|
||||
with ContentSwitcher(initial="files", id="sidebar-switcher"):
|
||||
with Vertical(id="files"):
|
||||
yield Static("EXPLORER")
|
||||
yield CustomDirectoryTree(self.path, id="directory")
|
||||
|
||||
with Vertical(id="editor"):
|
||||
first_tab = Tab("New File")
|
||||
first_tab.file_path = None
|
||||
with Vertical(id="editor"):
|
||||
first_tab = Tab("New File")
|
||||
first_tab.file_path = None
|
||||
|
||||
yield Tabs(
|
||||
first_tab,
|
||||
id="file-tabs"
|
||||
)
|
||||
yield TextArea.code_editor(placeholder="This file is empty.", theme="css", id="code-editor", disabled=True, soft_wrap=True, show_line_numbers=bool(int(self.config_handler.get("editor", "line_numbers"))))
|
||||
yield Tabs(
|
||||
first_tab,
|
||||
id="file-tabs"
|
||||
)
|
||||
yield TextArea.code_editor(placeholder="This file is empty.", theme="css", id="code-editor", disabled=True, soft_wrap=True, show_line_numbers=bool(int(self.config_handler.get("editor", "line_numbers"))))
|
||||
|
||||
#if os.name == "nt":
|
||||
#with Vertical(id="console-container"):
|
||||
# yield RichLog(id="console")
|
||||
# yield Input(placeholder="> ", id="console-input")
|
||||
#else:
|
||||
# yield Terminal(command="bash", id="terminal")
|
||||
#if os.name == "nt":
|
||||
#with Vertical(id="console-container"):
|
||||
# yield RichLog(id="console")
|
||||
# yield Input(placeholder="> ", id="console-input")
|
||||
#else:
|
||||
# yield Terminal(command="bash", id="terminal")
|
||||
|
||||
yield Footer()
|
||||
if bool(int(self.config_handler.get("plugins", "enabled"))) == True:
|
||||
yield PluginLoader()
|
||||
yield Footer()
|
||||
if bool(int(self.config_handler.get("plugins", "enabled"))) == True:
|
||||
yield PluginLoader()
|
||||
|
||||
def action_settings(self):
|
||||
self.push_screen(SettingsScreen())
|
||||
def action_settings(self):
|
||||
self.push_screen(SettingsScreen())
|
||||
|
||||
def get_system_commands(self, screen):
|
||||
yield SystemCommand(
|
||||
"Quit the application",
|
||||
"Quit the application as soon as possible",
|
||||
self.action_quit,
|
||||
)
|
||||
def get_system_commands(self, screen):
|
||||
yield SystemCommand(
|
||||
"Quit the application",
|
||||
"Quit the application as soon as possible",
|
||||
self.action_quit,
|
||||
)
|
||||
|
||||
if screen.query("HelpPanel"):
|
||||
yield SystemCommand(
|
||||
"Hide keys and help panel",
|
||||
"Hide the keys and widget help panel",
|
||||
self.action_hide_help_panel,
|
||||
)
|
||||
else:
|
||||
yield SystemCommand(
|
||||
"Show keys and help panel",
|
||||
"Show help for the focused widget and a summary of available keys",
|
||||
self.action_show_help_panel,
|
||||
)
|
||||
|
||||
yield SystemCommand("Settings", "Open the settings menu", self.action_settings)
|
||||
if screen.query("HelpPanel"):
|
||||
yield SystemCommand(
|
||||
"Hide keys and help panel",
|
||||
"Hide the keys and widget help panel",
|
||||
self.action_hide_help_panel,
|
||||
)
|
||||
else:
|
||||
yield SystemCommand(
|
||||
"Show keys and help panel",
|
||||
"Show help for the focused widget and a summary of available keys",
|
||||
self.action_show_help_panel,
|
||||
)
|
||||
|
||||
yield SystemCommand("Settings", "Open the settings menu", self.action_settings)
|
||||
|
||||
async def chose_file_to_open(self, result):
|
||||
if result == None: return
|
||||
result = str(result)
|
||||
if self.open_file == result:
|
||||
return
|
||||
|
||||
def is_within_directory(file_path: str, directory: str) -> bool:
|
||||
file_path = Path(file_path).resolve()
|
||||
directory = Path(directory).resolve()
|
||||
return directory in file_path.parents
|
||||
|
||||
self.switching = True
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
|
||||
if self.open_file not in self.unsaved_files:
|
||||
if self.open_file:
|
||||
self.file_tabs.pop(self.open_file)
|
||||
tabs.remove_tab(tabs.active_tab)
|
||||
|
||||
self.open_file = result
|
||||
inside_dir = is_within_directory(result, self.path)
|
||||
async def chose_file_to_open(self, result):
|
||||
if result == None: return
|
||||
result = str(result)
|
||||
if self.open_file == result:
|
||||
return
|
||||
|
||||
def is_within_directory(file_path: str, directory: str) -> bool:
|
||||
file_path = Path(file_path).resolve()
|
||||
directory = Path(directory).resolve()
|
||||
return directory in file_path.parents
|
||||
|
||||
self.switching = True
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
|
||||
if self.open_file not in self.unsaved_files:
|
||||
if self.open_file:
|
||||
self.file_tabs.pop(self.open_file)
|
||||
tabs.remove_tab(tabs.active_tab)
|
||||
|
||||
self.open_file = result
|
||||
inside_dir = is_within_directory(result, self.path)
|
||||
|
||||
self.sub_title = os.path.basename(self.open_file) if inside_dir else self.open_file
|
||||
self.sub_title = os.path.basename(self.open_file) if inside_dir else self.open_file
|
||||
|
||||
if result not in self.file_tabs:
|
||||
new_tab = Tab(os.path.basename(result) if inside_dir else result)
|
||||
new_tab.tooltip = str(new_tab.label)
|
||||
setattr(new_tab, "file_path", result)
|
||||
await tabs.add_tab(new_tab)
|
||||
self.file_tabs[result] = new_tab
|
||||
tabs.active = new_tab.id
|
||||
else:
|
||||
tabs.active = self.file_tabs[result].id
|
||||
if result not in self.file_tabs:
|
||||
new_tab = Tab(os.path.basename(result) if inside_dir else result)
|
||||
new_tab.tooltip = str(new_tab.label)
|
||||
setattr(new_tab, "file_path", result)
|
||||
await tabs.add_tab(new_tab)
|
||||
self.file_tabs[result] = new_tab
|
||||
tabs.active = new_tab.id
|
||||
else:
|
||||
tabs.active = self.file_tabs[result].id
|
||||
|
||||
def action_open(self):
|
||||
self.app.push_screen(FileOpen(), self.chose_file_to_open)
|
||||
def action_open(self):
|
||||
self.app.push_screen(FileOpen(), self.chose_file_to_open)
|
||||
|
||||
def action_find(self):
|
||||
try:
|
||||
self.query_one("#find-window")
|
||||
return
|
||||
except:
|
||||
def action_find(self):
|
||||
try:
|
||||
self.query_one("#find-window")
|
||||
return
|
||||
except:
|
||||
|
||||
find_window = Window(
|
||||
find_window = Window(
|
||||
|
||||
Vertical(
|
||||
HorizontalGroup(
|
||||
Input(placeholder="Find"),
|
||||
Static("0 of 0", id="num-matches"),
|
||||
Button("↑", flat=True),
|
||||
Button("↓", flat=True),
|
||||
),
|
||||
HorizontalGroup(
|
||||
Input(placeholder="Replace"),
|
||||
),
|
||||
),
|
||||
|
||||
icon="🔍",
|
||||
start_open=True,
|
||||
allow_resize=False,
|
||||
allow_maximize=False,
|
||||
id="find-window",
|
||||
mode="temporary",
|
||||
name="Find & Replace"
|
||||
)
|
||||
Vertical(
|
||||
HorizontalGroup(
|
||||
Input(placeholder="Find"),
|
||||
Static("0 of 0", id="num-matches"),
|
||||
Button("↑", flat=True),
|
||||
Button("↓", flat=True),
|
||||
),
|
||||
HorizontalGroup(
|
||||
Input(placeholder="Replace"),
|
||||
),
|
||||
),
|
||||
|
||||
icon="🔍",
|
||||
start_open=True,
|
||||
allow_resize=False,
|
||||
allow_maximize=False,
|
||||
id="find-window",
|
||||
mode="temporary",
|
||||
name="Find & Replace"
|
||||
)
|
||||
|
||||
self.mount(find_window)
|
||||
self.mount(find_window)
|
||||
|
||||
async def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected):
|
||||
if self.open_file == str(event.path):
|
||||
return
|
||||
async def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected):
|
||||
if self.open_file == str(event.path):
|
||||
return
|
||||
|
||||
self.file_clicked = True
|
||||
self.switching = True
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
|
||||
if self.open_file not in self.unsaved_files:
|
||||
if self.open_file:
|
||||
self.file_tabs.pop(self.open_file)
|
||||
tabs.remove_tab(tabs.active_tab)
|
||||
|
||||
self.open_file = str(event.path)
|
||||
self.sub_title = os.path.basename(self.open_file)
|
||||
self.file_clicked = True
|
||||
self.switching = True
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
|
||||
if self.open_file not in self.unsaved_files:
|
||||
if self.open_file:
|
||||
self.file_tabs.pop(self.open_file)
|
||||
tabs.remove_tab(tabs.active_tab)
|
||||
|
||||
self.open_file = str(event.path)
|
||||
self.sub_title = os.path.basename(self.open_file)
|
||||
|
||||
if str(event.path) not in self.file_tabs:
|
||||
new_tab = Tab(os.path.basename(str(event.path)))
|
||||
new_tab.tooltip = str(new_tab.label)
|
||||
setattr(new_tab, "file_path", str(event.path))
|
||||
await tabs.add_tab(new_tab)
|
||||
self.file_tabs[str(event.path)] = new_tab
|
||||
tabs.active = new_tab.id
|
||||
else:
|
||||
tabs.active = self.file_tabs[str(event.path)].id
|
||||
if str(event.path) not in self.file_tabs:
|
||||
new_tab = Tab(os.path.basename(str(event.path)))
|
||||
new_tab.tooltip = str(new_tab.label)
|
||||
setattr(new_tab, "file_path", str(event.path))
|
||||
await tabs.add_tab(new_tab)
|
||||
self.file_tabs[str(event.path)] = new_tab
|
||||
tabs.active = new_tab.id
|
||||
else:
|
||||
tabs.active = self.file_tabs[str(event.path)].id
|
||||
|
||||
|
||||
def open_text_editor(self, file_content: bytes, file_extension: str):
|
||||
code_editor: TextArea = self.query_one("#code-editor")
|
||||
|
||||
code_editor.language = theme_mappings.get(file_extension, None)
|
||||
|
||||
try:
|
||||
code_editor.text = file_content.decode("utf-8")
|
||||
code_editor.disabled = False
|
||||
except UnicodeDecodeError:
|
||||
code_editor.text = "This file is in binary, it can't be opened. Sorry."
|
||||
code_editor.language = None
|
||||
code_editor.disabled = True
|
||||
|
||||
code_editor.focus()
|
||||
|
||||
def open_editor(self, file_extension: str, file_content: str):
|
||||
default_editors: dict = eval(self.config_handler.get("editor", "default_editors"))
|
||||
editor_for_file = default_editors.get(file_extension, None)
|
||||
|
||||
if not editor_for_file:
|
||||
self.open_text_editor(file_content, file_extension)
|
||||
|
||||
@on(Tabs.TabActivated)
|
||||
def on_tab_shown(self, event: Tabs.TabActivated):
|
||||
|
||||
if self.file_clicked:
|
||||
self.file_clicked = False
|
||||
else:
|
||||
self.open_file = getattr(event.tab, "file_path")
|
||||
|
||||
self.switching = True
|
||||
|
||||
content = b""
|
||||
file_extension = ""
|
||||
|
||||
if self.open_file:
|
||||
try:
|
||||
f = open(self.open_file, "rb")
|
||||
except Exception as e:
|
||||
self.notify(f"Failed to open the file: {e}")
|
||||
return
|
||||
|
||||
# if the file is unsaved, use the content we wrote into it
|
||||
# otherwise, read the CURRENT state of the file
|
||||
if self.open_file in self.unsaved_files:
|
||||
content = self.unsaved_files[self.open_file]["current"]
|
||||
else:
|
||||
content = f.read()
|
||||
|
||||
# get the file extension
|
||||
file_extension = self.open_file.split(".")[-1] if "." in self.open_file else ""
|
||||
|
||||
# close the file
|
||||
f.close()
|
||||
|
||||
self.open_editor(file_extension, content)
|
||||
|
||||
|
||||
|
||||
|
||||
@on(Tabs.TabActivated)
|
||||
def on_tab_shown(self, event: Tabs.TabActivated):
|
||||
@on(Window.Minimized)
|
||||
def window_minimized(self, event: Window.Minimized):
|
||||
event.window.remove_window()
|
||||
|
||||
def on_text_area_changed(self, event: TextArea.Changed):
|
||||
if event.text_area.id != "code-editor":
|
||||
return
|
||||
if self.switching:
|
||||
self.switching = False
|
||||
return
|
||||
|
||||
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
if self.open_file:
|
||||
if not self.open_file in self.unsaved_files:
|
||||
with open(self.open_file, "r", encoding="utf-8") as f:
|
||||
if f.read() == event.text_area.text: # TODO: figure out why im guetting what seems like a race conidition which is making this if statement needed
|
||||
return
|
||||
self.unsaved_files[self.open_file] = {"current": event.text_area.text, "original": f.read()}
|
||||
|
||||
if self.file_clicked:
|
||||
self.file_clicked = False
|
||||
else:
|
||||
self.open_file = getattr(event.tab, "file_path")
|
||||
|
||||
tabs.active_tab.tooltip = f"Unsaved changes in {tabs.active_tab.label}"
|
||||
tabs.active_tab.label = "[d orange]●[/] " + str(tabs.active_tab.label)
|
||||
|
||||
else:
|
||||
self.unsaved_files[self.open_file]["current"] = event.text_area.text
|
||||
if self.unsaved_files[self.open_file]["original"] == self.unsaved_files[self.open_file]["current"]:
|
||||
tabs.active_tab.label = os.path.basename(self.open_file)
|
||||
tabs.active_tab.tooltip = str(tabs.active_tab.label)
|
||||
self.unsaved_files.pop(self.open_file)
|
||||
|
||||
self.switching = True
|
||||
def action_new(self):
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
new_tab = Tab("New File")
|
||||
setattr(new_tab, "file_path", None)
|
||||
tabs.add_tab(new_tab)
|
||||
tabs.active = new_tab.id
|
||||
|
||||
code_editor: TextArea = self.query_one("#code-editor")
|
||||
if self.open_file:
|
||||
try:
|
||||
f = open(self.open_file, "r", encoding="utf-8")
|
||||
except Exception as e:
|
||||
self.notify(f"Failed to open the file: {e}")
|
||||
return
|
||||
self.open_file = None
|
||||
self.switching = True
|
||||
code_editor: TextArea = self.query_one("#code-editor")
|
||||
|
||||
|
||||
code_editor.disabled = False
|
||||
code_editor.text = ""
|
||||
|
||||
|
||||
try:
|
||||
if self.open_file in self.unsaved_files:
|
||||
code_editor.text = self.unsaved_files[self.open_file]["current"]
|
||||
else:
|
||||
code_editor.text = f.read()
|
||||
|
||||
file_extension = self.open_file
|
||||
dot_count = file_extension.count(".")
|
||||
|
||||
if dot_count == 1:
|
||||
if file_extension.startswith("."):
|
||||
file_extension = file_extension.removeprefix(".")
|
||||
else:
|
||||
file_extension = file_extension.rsplit(".", 1)[1]
|
||||
elif dot_count > 1:
|
||||
file_extension = file_extension.rsplit(".", 1)[1]
|
||||
def done_saving(self, result):
|
||||
if result is None: return
|
||||
|
||||
code_editor.language = theme_mappings.get(file_extension, None)
|
||||
code_editor.disabled = False
|
||||
except UnicodeDecodeError:
|
||||
code_editor.text = "This file is in binary, it can't be opened. Sorry."
|
||||
code_editor.language = None
|
||||
code_editor.disabled = True
|
||||
with open(result, "w", encoding="utf-8") as f:
|
||||
f.write(self.query_one("#code-editor").text)
|
||||
|
||||
f.close()
|
||||
else:
|
||||
code_editor.text = ""
|
||||
code_editor.language = None
|
||||
code_editor.disabled = False
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
tabs.active_tab.label = os.path.basename(result)
|
||||
self.notify(f"Saved to {result} successfully.", title="Done!", markup=False)
|
||||
self.query_one(DirectoryTree).reload()
|
||||
|
||||
code_editor.focus()
|
||||
def action_save_as(self):
|
||||
self.push_screen(FileSave(), callback=self.done_saving)
|
||||
|
||||
def action_save(self):
|
||||
if self.open_file == None and self.query_one("#code-editor").disabled == False:
|
||||
self.action_save_as()
|
||||
|
||||
@on(Window.Minimized)
|
||||
def window_minimized(self, event: Window.Minimized):
|
||||
event.window.remove_window()
|
||||
|
||||
def on_text_area_changed(self, event: TextArea.Changed):
|
||||
if event.text_area.id != "code-editor":
|
||||
return
|
||||
if self.switching:
|
||||
self.switching = False
|
||||
return
|
||||
|
||||
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
if self.open_file:
|
||||
if not self.open_file in self.unsaved_files:
|
||||
with open(self.open_file, "r", encoding="utf-8") as f:
|
||||
if f.read() == event.text_area.text: # TODO: figure out why im guetting what seems like a race conidition which is making this if statement needed
|
||||
return
|
||||
self.unsaved_files[self.open_file] = {"current": event.text_area.text, "original": f.read()}
|
||||
# dont bother saving if there are no new changes
|
||||
if not self.open_file in self.unsaved_files: return
|
||||
|
||||
|
||||
tabs.active_tab.tooltip = f"Unsaved changes in {tabs.active_tab.label}"
|
||||
tabs.active_tab.label = "[d orange]●[/] " + str(tabs.active_tab.label)
|
||||
|
||||
else:
|
||||
self.unsaved_files[self.open_file]["current"] = event.text_area.text
|
||||
if self.unsaved_files[self.open_file]["original"] == self.unsaved_files[self.open_file]["current"]:
|
||||
tabs.active_tab.label = os.path.basename(self.open_file)
|
||||
tabs.active_tab.tooltip = str(tabs.active_tab.label)
|
||||
self.unsaved_files.pop(self.open_file)
|
||||
with open(self.open_file, "w") as f:
|
||||
f.write(self.unsaved_files[self.open_file]["current"])
|
||||
|
||||
def action_new(self):
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
new_tab = Tab("New File")
|
||||
setattr(new_tab, "file_path", None)
|
||||
tabs.add_tab(new_tab)
|
||||
tabs.active = new_tab.id
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
tabs.active_tab.label = os.path.basename(self.open_file)
|
||||
tabs.active_tab.tooltip = str(tabs.active_tab.label)
|
||||
self.unsaved_files.pop(self.open_file)
|
||||
self.notify("Saved.")
|
||||
|
||||
self.open_file = None
|
||||
self.switching = True
|
||||
code_editor: TextArea = self.query_one("#code-editor")
|
||||
def action_quit(self):
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
return super().action_quit()
|
||||
|
||||
def on_ready(self):
|
||||
# src/main.py: Tab<>
|
||||
self.file_tabs = {}
|
||||
self.open_file = None
|
||||
self.unsaved_files = {} # list of paths
|
||||
self.switching = False
|
||||
self.file_clicked = False
|
||||
|
||||
code_editor.disabled = False
|
||||
code_editor.text = ""
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(Watcher(self), path=self.path)
|
||||
self.observer.start()
|
||||
|
||||
def done_saving(self, result):
|
||||
if result is None: return
|
||||
|
||||
with open(result, "w", encoding="utf-8") as f:
|
||||
f.write(self.query_one("#code-editor").text)
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
tabs.active_tab.label = os.path.basename(result)
|
||||
self.notify(f"Saved to {result} successfully.", title="Done!", markup=False)
|
||||
self.query_one(DirectoryTree).reload()
|
||||
|
||||
def action_save_as(self):
|
||||
self.push_screen(FileSave(), callback=self.done_saving)
|
||||
|
||||
def action_save(self):
|
||||
if self.open_file == None and self.query_one("#code-editor").disabled == False:
|
||||
self.action_save_as()
|
||||
|
||||
# dont bother saving if there are no new changes
|
||||
if not self.open_file in self.unsaved_files: return
|
||||
|
||||
with open(self.open_file, "w") as f:
|
||||
f.write(self.unsaved_files[self.open_file]["current"])
|
||||
|
||||
tabs: Tabs = self.query_one("#file-tabs")
|
||||
tabs.active_tab.label = os.path.basename(self.open_file)
|
||||
tabs.active_tab.tooltip = str(tabs.active_tab.label)
|
||||
self.unsaved_files.pop(self.open_file)
|
||||
self.notify("Saved.")
|
||||
|
||||
def action_quit(self):
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
return super().action_quit()
|
||||
|
||||
def on_ready(self):
|
||||
# src/main.py: Tab<>
|
||||
self.file_tabs = {}
|
||||
self.open_file = None
|
||||
self.unsaved_files = {} # list of paths
|
||||
self.switching = False
|
||||
self.file_clicked = False
|
||||
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(Watcher(self), path=self.path)
|
||||
self.observer.start()
|
||||
|
||||
self.config_handler.apply_settings()
|
||||
self.config_handler.apply_settings()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
working_path = os.getcwd() if len(sys.argv) == 1 else sys.argv[1]
|
||||
app = Berry(working_path)
|
||||
app.run()
|
||||
working_path = os.getcwd() if len(sys.argv) == 1 else sys.argv[1]
|
||||
app = Berry(working_path)
|
||||
app.run()
|
||||
@@ -71,7 +71,14 @@ class PluginLoader(Window):
|
||||
setattr(self.app, f"action_{action_name}", wrapper)
|
||||
|
||||
def create_widget(self, widget_type: str, *args):
|
||||
return getattr(textual.widgets, widget_type)(*args)
|
||||
new_widget = getattr(textual.widgets, widget_type)(*args)
|
||||
|
||||
def on_func(event_name: str, function):
|
||||
self.event_handler.subscribe(new_widget, event_name, function)
|
||||
new_widget.message_signal.subscribe(self, lambda e: self.event_handler.message_handler(new_widget, e))
|
||||
|
||||
setattr(new_widget, "on", on_func)
|
||||
return new_widget
|
||||
|
||||
def run_on_message(self, widget, event, function):
|
||||
self.event_handler.subscribe(widget, event, function)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
local plugin = {}
|
||||
|
||||
function plugin.run()
|
||||
--berry.config.defineSetting("my plugin", "test setting", "description", "boolean", "1")
|
||||
|
||||
button = berry.ui.button("Click me!", "success")
|
||||
berry.ui.mount(button)
|
||||
|
||||
berry.ui.onMessage(button, "Pressed", function()
|
||||
print("hi")
|
||||
end)
|
||||
end
|
||||
|
||||
return plugin
|
||||
7
plugins/Markdown Reader/lua/main.lua
Normal file
7
plugins/Markdown Reader/lua/main.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local plugin = {}
|
||||
|
||||
function plugin.run()
|
||||
berry.ui.notify("hi lmao")
|
||||
end
|
||||
|
||||
return plugin
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Git",
|
||||
"name": "Markdown Reader",
|
||||
"author": "SpookyDervish",
|
||||
"version": "1.0.0",
|
||||
"dependencies": []
|
||||
127
prompt.py
127
prompt.py
@@ -1,80 +1,85 @@
|
||||
from textual.screen import ModalScreen
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Static, Button, Input
|
||||
from textual.widgets import Static, Button, Input, Select
|
||||
from textual.containers import HorizontalGroup, Center
|
||||
from textual.binding import Binding
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Prompt(ModalScreen):
|
||||
DEFAULT_CSS = """
|
||||
Prompt {
|
||||
align: center middle;
|
||||
DEFAULT_CSS = """
|
||||
Prompt {
|
||||
align: center middle;
|
||||
|
||||
#window {
|
||||
max-width: 50;
|
||||
height: auto;
|
||||
border: panel $accent;
|
||||
#window {
|
||||
max-width: 50;
|
||||
height: auto;
|
||||
border: panel $accent;
|
||||
|
||||
#question {
|
||||
width: 100%;
|
||||
margin: 2;
|
||||
margin-top: 1;
|
||||
}
|
||||
#question {
|
||||
width: 100%;
|
||||
margin: 2;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
#bottom {
|
||||
dock: bottom;
|
||||
margin-bottom: 1;
|
||||
margin-left: 1;
|
||||
#bottom {
|
||||
dock: bottom;
|
||||
margin-bottom: 1;
|
||||
margin-left: 1;
|
||||
|
||||
#yes {
|
||||
margin-left: 1;
|
||||
margin-right: 2;
|
||||
}
|
||||
#yes {
|
||||
margin-left: 1;
|
||||
margin-right: 2;
|
||||
}
|
||||
|
||||
#confirm-string {
|
||||
margin-left: 1;
|
||||
}
|
||||
#confirm-string {
|
||||
margin-left: 1;
|
||||
}
|
||||
|
||||
Input {
|
||||
max-width: 25;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Input {
|
||||
max-width: 25;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
Binding("escape", "close", "Close")
|
||||
]
|
||||
BINDINGS = [
|
||||
Binding("escape", "close", "Close")
|
||||
]
|
||||
|
||||
def action_close(self):
|
||||
self.dismiss(None)
|
||||
def action_close(self):
|
||||
self.dismiss(None)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed):
|
||||
if event.button.id == "yes":
|
||||
self.dismiss(True)
|
||||
elif event.button.id == "no":
|
||||
self.dismiss(False)
|
||||
elif event.button.id == "confirm-string":
|
||||
self.dismiss(self.query_one("#input").value)
|
||||
def on_button_pressed(self, event: Button.Pressed):
|
||||
if event.button.id == "yes":
|
||||
self.dismiss(True)
|
||||
elif event.button.id == "no":
|
||||
self.dismiss(False)
|
||||
elif event.button.id == "confirm-string":
|
||||
self.dismiss(self.query_one("#input").value)
|
||||
elif event.button.id == "confirm-list":
|
||||
self.dismiss(self.query_one("#select").value)
|
||||
|
||||
def __init__(self, question: str, prompt_type: Literal["confirm", "string"], title: str = "Confirm"):
|
||||
super().__init__()
|
||||
self.question = question
|
||||
self.window_title = title
|
||||
self.prompt_type = prompt_type
|
||||
def __init__(self, question: str, prompt_type: Literal["confirm", "string"], title: str = "Confirm", **kwargs):
|
||||
super().__init__()
|
||||
self.question = question
|
||||
self.window_title = title
|
||||
self.prompt_type = prompt_type
|
||||
self.kwargs = kwargs
|
||||
|
||||
def compose(self):
|
||||
with Vertical(id="window") as window:
|
||||
window.border_title = self.window_title
|
||||
yield Static(self.question, id="question")
|
||||
|
||||
if self.prompt_type == "confirm":
|
||||
with HorizontalGroup(id="bottom"):
|
||||
yield Button("Yes", variant="success", id="yes")
|
||||
yield Button("No", variant="error", id="no")
|
||||
elif self.prompt_type == "string":
|
||||
with HorizontalGroup(id="bottom"):
|
||||
yield Input(id="input", max_length=30)
|
||||
yield Button("Confirm", variant="success", id="confirm-string")
|
||||
def compose(self):
|
||||
with Vertical(id="window") as window:
|
||||
window.border_title = self.window_title
|
||||
yield Static(self.question, id="question")
|
||||
|
||||
with HorizontalGroup(id="bottom"):
|
||||
if self.prompt_type == "confirm":
|
||||
yield Button("Yes", variant="success", id="yes")
|
||||
yield Button("No", variant="error", id="no")
|
||||
elif self.prompt_type == "string":
|
||||
yield Input(id="input", **self.kwargs)
|
||||
yield Button("Confirm", variant="success", id="confirm-string")
|
||||
elif self.prompt_type == "list":
|
||||
yield Select.from_values(**self.kwargs, id="select")
|
||||
yield Button("Confirm", variant="success", id="confirm-list")
|
||||
@@ -93,7 +93,8 @@ class ConfigHandler:
|
||||
}
|
||||
self.config["editor"] = {
|
||||
"word_wrap": "0",
|
||||
"line_numbers": "1"
|
||||
"line_numbers": "1",
|
||||
"default_editors": {}
|
||||
}
|
||||
self.config["plugins"] = {
|
||||
"enabled": "1",
|
||||
|
||||
Reference in New Issue
Block a user