diff --git a/src/main.py b/src/main.py index bd4d0e5..a64ed16 100644 --- a/src/main.py +++ b/src/main.py @@ -5,7 +5,7 @@ from textual.widgets import Header, Footer, TabPane, TabbedContent from textual.containers import Vertical from widgets.text_editor import TextEditor, TextArea from widgets.file_tabs import FileTabs -from widgets.directory_tree_custom import CustomDirectoryTree +from widgets.sidebar import Sidebar import sys, os import argparse @@ -28,6 +28,8 @@ class Berry(App): "save": "ctrl+s", "save-as": "ctrl+shift+s", "settings": "ctrl+f1", + "home": "ctrl+h", + "find": "ctrl+f" } @@ -42,7 +44,9 @@ class Berry(App): Binding(self.bind_keys['open'], "open_file", "Open file"), Binding(self.bind_keys['open-folder'], "open_folder", "Open directory"), Binding(self.bind_keys['save'], "save", "Save"), - Binding(self.bind_keys['save-as'], "save_as", "Save as...") + Binding(self.bind_keys['save-as'], "save_as", "Save as..."), + Binding(self.bind_keys['home'], "home", "Home"), + Binding(self.bind_keys['find'], "find", "Find and replace") ) for bind in self.BINDINGS: @@ -53,16 +57,25 @@ class Berry(App): yield Header() with Vertical(id="main"): - yield CustomDirectoryTree(self.path.parent if self.path.is_file() else self.path) + yield Sidebar() self.file_tabs = FileTabs() yield self.file_tabs yield Footer() + async def open_file(self, path: str): + self.path = pathlib.Path(path) + + if not self.path.exists(): + self.notify("That file doesn't exist.", title="File not found", severity="error") + return + + await self.file_tabs.open_file(self.path) + async def on_ready(self) -> None: if self.path.is_file(): - await self.file_tabs.open_file(self.path) + await self.open_file(self.path) return await self.file_tabs.open_home() diff --git a/src/widgets/directory_tree_custom.py b/src/widgets/directory_tree_custom.py index bb0f410..cbc40e9 100644 --- a/src/widgets/directory_tree_custom.py +++ b/src/widgets/directory_tree_custom.py @@ -12,7 +12,6 @@ class CustomDirectoryTree(DirectoryTree): DEFAULT_CSS = """ CustomDirectoryTree { - dock: left; width: 30; } """ @@ -132,3 +131,5 @@ class CustomDirectoryTree(DirectoryTree): event.screen_offset ), lambda result: self.context_menu_chosen(file_name, result)) + async def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected): + await self.app.open_file(event.path) \ No newline at end of file diff --git a/src/widgets/file_tabs.py b/src/widgets/file_tabs.py index ffdbd1e..92f81d2 100644 --- a/src/widgets/file_tabs.py +++ b/src/widgets/file_tabs.py @@ -1,11 +1,50 @@ from textual.widgets import TabbedContent, TabPane +from textual.reactive import reactive from widgets.text_editor import TextEditor from widgets.home_page import HomePage -import os +import os, pathlib +FILE_EXTENSION_LANG = { + ".py": "python" +} + + +class FileTab(TabPane): + saved = reactive(True) + changed = reactive(False) + + def __init__(self, path: str): + file_name = os.path.basename(path) + + super().__init__(f"[ansi_bright_green][/] {file_name}") + self.file_name = file_name + self.path = path + + def watch_saved(self, saved_value: bool): + if self.saved == saved_value: + return + + self.changed = True + icon = "[ansi_bright_green][/]" if self.saved else "[ansi_yellow][/]" + + self._title = self.render_str(f"{icon} {self.file_name}") + parent: TabbedContent = self.parent + + parent.recompose() + + def on_text_area_changed(self, event: TextEditor.Changed): + self.saved = False + +class HomeTab(TabPane): + def __init__(self): + super().__init__("Home") + + def compose(self): + yield HomePage() + class FileTabs(TabbedContent): DEFAULT_CSS = """ FileTabs { @@ -13,22 +52,60 @@ class FileTabs(TabbedContent): } """ - async def open_home(self) -> None: - new_pane = TabPane("Home") - new_pane.path = None + async def save(self) -> None: + if self.tab_count == 0: + return - await self.add_pane(new_pane) - await new_pane.mount(HomePage()) + if not isinstance(self.active_pane, FileTab): + return + + file_tab: FileTab = self.active_pane + + text_editor: TextEditor = file_tab.query_one(TextEditor) + if text_editor.read_only: + return + + with open(file_tab.path, "w") as f: + f.write(text_editor.text) + + file_tab.saved = True + + async def open_home(self) -> None: + await self.add_pane(HomeTab()) async def open_file(self, path: str) -> None: if self.tab_count > 0: - for tab in self.query(TabPane): - if tab.path == path: - self.active = tab.id - return + if isinstance(self.active_pane, HomeTab): + self.remove_pane(self.active) + else: + if not self.active_pane.changed: + self.remove_pane(self.active) + + for tab in self.query(TabPane): + if tab.path == path: + self.active = tab.id + return - new_pane = TabPane(os.path.basename(path)) - new_pane.path = path + new_pane = FileTab(path) + + path = pathlib.Path(path) + + content: str = "" + is_binary: bool = False + try: + with open(path, "r") as f: + content = f.read() + except UnicodeDecodeError: + is_binary = True await self.add_pane(new_pane) - await new_pane.mount(TextEditor()) \ No newline at end of file + + text_editor: TextEditor = None + if not is_binary: + text_editor = TextEditor(content, language=FILE_EXTENSION_LANG.get(path.suffix, None)) + else: + text_editor = TextEditor(is_binary=is_binary) + await new_pane.mount(text_editor) + + self.active = new_pane.id + \ No newline at end of file diff --git a/src/widgets/home_page.py b/src/widgets/home_page.py index 45fe7b4..aef4e37 100644 --- a/src/widgets/home_page.py +++ b/src/widgets/home_page.py @@ -18,7 +18,7 @@ class HomePage(Vertical): } #banner { - height: 6; # probs will need to be updated + height: 7; # probs will need to be updated margin-bottom: 1; Rule { diff --git a/src/widgets/sidebar.py b/src/widgets/sidebar.py new file mode 100644 index 0000000..70dbf27 --- /dev/null +++ b/src/widgets/sidebar.py @@ -0,0 +1,53 @@ +from textual.containers import Vertical, Container +from textual.widgets import Button, ContentSwitcher, Static + +from widgets.directory_tree_custom import CustomDirectoryTree + + +class Sidebar(Container): + DEFAULT_CSS = """ + Sidebar { + dock: left; + height: 1fr; + width: auto; + layout: horizontal; + + #sidebar-buttons { + padding: 1; + border: $surface tall; + width: 11; + + Button { + max-width: 7; + margin-bottom: 1; + color: $primary; + text-style: bold; + + &:focus { + text-style: bold; + } + } + } + + ContentSwitcher { + width: auto; + } + } + """ + + def on_button_pressed(self, event: Button.Pressed): + content_switcher: ContentSwitcher = self.query_one(ContentSwitcher) + + if content_switcher.current == event.button.id: + content_switcher.current = None + return + + content_switcher.current = event.button.id + + def compose(self): + with Vertical(id="sidebar-buttons"): + yield Button("", id="files") + yield Button("󰐱", id="plugins") + with ContentSwitcher(): + yield CustomDirectoryTree(".", id="files") + yield Static("sorry, plugins aren't not done yet", id="plugins") \ No newline at end of file diff --git a/src/widgets/text_editor.py b/src/widgets/text_editor.py index 433e690..d21b27e 100644 --- a/src/widgets/text_editor.py +++ b/src/widgets/text_editor.py @@ -2,5 +2,9 @@ from textual.widgets import TextArea class TextEditor(TextArea): - def __init__(self, text = "", language = None, read_only = False): - super().__init__(text, language=language, placeholder="Type here . . .", read_only=read_only, theme="vscode_dark", show_line_numbers=True, tab_behavior="indent") \ No newline at end of file + def __init__(self, text = "", language = None, read_only = False, is_binary: bool = False): + if is_binary: + super().__init__(placeholder="This file is binary and not be read.", read_only=True, theme="css", tab_behavior="indent") + return + + super().__init__(text, language=language, placeholder="Type here...", read_only=read_only, soft_wrap=False, theme="css", show_line_numbers=True, tab_behavior="indent") \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index 435bbfa..0000000 --- a/test.py +++ /dev/null @@ -1,13 +0,0 @@ -import pyfiglet - -text_to_render = "Test" -fonts = pyfiglet.FigletFont.getFonts() - -for font in fonts: - try: - print(f"\nFont: {font}") - result = pyfiglet.figlet_format(text_to_render, font=font) - print(result) - except Exception: - # Prevents the script from crashing if a specific font configuration fails - continue \ No newline at end of file