add sidebar and start adding functionality

This commit is contained in:
2026-07-28 15:05:57 +10:00
parent de7e3b9070
commit 44aa152295
7 changed files with 169 additions and 34 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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:
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())
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

View File

@@ -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 {

53
src/widgets/sidebar.py Normal file
View File

@@ -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")

View File

@@ -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")
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")

13
test.py
View File

@@ -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