add sidebar and start adding functionality
This commit is contained in:
21
src/main.py
21
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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
|
||||
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
|
||||
|
||||
@@ -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
53
src/widgets/sidebar.py
Normal 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")
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user