Skip to content
This repository was archived by the owner on Jul 3, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Byte-compiled / optimized / DLL files
.DS_Store
__pycache__/
*.py[cod]
*$py.class
Expand Down
106 changes: 93 additions & 13 deletions src/gitx/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container
from textual.widgets import Header, Footer, Static
from textual.containers import Container, Grid
from textual.widgets import Header, Footer

from gitx.widgets.status_panel import StatusPanel
from gitx.widgets.file_tree import FileTree
from gitx.widgets.commit_log import CommitLog
from gitx.widgets.branches_panel import BranchesPanel
from gitx.widgets.command_panel import CommandPanel
from gitx.widgets.main_panel import MainPanel
from gitx.git.handler import GitHandler


class GitxApp(App):
Expand All @@ -11,27 +19,99 @@ class GitxApp(App):

BINDINGS = [
Binding(key="q", action="quit", description="Quit"),
Binding(key="t", action="toggle_dark", description="Toggle dark mode"),
Binding(key="t", action="toggle_theme", description="Toggle theme"),
Binding(key="s", action="stage_file", description="Stage file"),
Binding(key="u", action="unstage_file", description="Unstage file"),
Binding(key="c", action="commit", description="Commit"),
Binding(key="p", action="push", description="Push"),
Binding(key="f", action="pull", description="Pull (fetch)"),
Binding(key="b", action="new_branch", description="New branch"),
Binding(key="?", action="toggle_help", description="Help"),
Binding(key="^p", action="palette", description="Command palette"),
]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.git = GitHandler()

def compose(self) -> ComposeResult:
"""Compose the app layout using the welcome screen."""
self.theme = "flexoki" # Default theme
"""Compose the app layout."""
yield Header(show_clock=True)
yield Container(
Static("Welcome to gitx!", classes="welcome-title"),
Static("A Terminal User Interface for Git", classes="welcome-text"),
Static("Press 't' to toggle theme or 'q' to quit", classes="welcome-text"),
classes="welcome"

# Left side - Status and Files
yield Grid(
# Left column - Status and file changes
Container(
StatusPanel(),
FileTree(),
BranchesPanel(),
id="left-panels"
),

# Right side - Main content area and logs
Container(
# Top right - Commit log
CommitLog(),
# Bottom right - Main content
MainPanel(),
id="right-panels"
),

# Command panel at bottom
Container(
CommandPanel(),
id="bottom-panel"
),

id="app-layout"
)

yield Footer()

def action_toggle_dark(self) -> None:
"""Toggle between light and dark theme."""
def on_mount(self) -> None:
"""Initial setup when app is mounted."""
self.title = "GitXApp"
self.dark = True

# Show welcome message in main panel
main_panel = self.query_one(MainPanel)
main_panel.show_welcome()

def action_toggle_theme(self) -> None:
"""Toggle dark mode."""
self.theme = (
"flexoki" if self.theme == "catppuccin-latte" else "catppuccin-latte"
"textual-dark" if self.theme == "textual-light" else "textual-light"
)

def action_stage_file(self) -> None:
"""Stage the selected file."""
self.notify("Action: Stage file (not implemented yet)")

def action_unstage_file(self) -> None:
"""Unstage the selected file."""
self.notify("Action: Unstage file (not implemented yet)")

def action_commit(self) -> None:
"""Commit staged changes."""
self.notify("Action: Commit (not implemented yet)")

def action_push(self) -> None:
"""Push changes to remote."""
self.notify("Action: Push (not implemented yet)")

def action_pull(self) -> None:
"""Pull changes from remote."""
self.notify("Action: Pull (not implemented yet)")

def action_new_branch(self) -> None:
"""Create a new branch."""
self.notify("Action: New branch (not implemented yet)")

def action_toggle_help(self) -> None:
"""Toggle help screen."""
self.notify("Action: Help (not implemented yet)")


def main() -> None:
"""Run the app."""
app = GitxApp()
Expand Down
172 changes: 156 additions & 16 deletions src/gitx/css/app.tcss
Original file line number Diff line number Diff line change
@@ -1,39 +1,179 @@
/* Base application styles */
/* Base application styles inspired by LazyGit */
Screen {
background: $surface;
color: $text;
layout: vertical;
background: #0d1117;
color: #c9d1d9;
}

Header {
dock: top;
height: 1;
background: #161b22;
color: #c9d1d9;
}

Footer {
dock: bottom;
height: 1;
background: #161b22;
color: #58a6ff;
}

/* Common styles that apply to the whole app */
.welcome {
width: 100%;
/* Grid layout */
#app-layout {
layout: grid;
grid-size: 2;
grid-rows: 1fr 3fr 1fr 1fr;
grid-columns: 1fr 2fr;
}

#left-panels {
row-span: 4;
column-span: 1;
layout: vertical;
background: #0d1117;
border-right: solid #30363d;
}

#right-panels {
row-span: 4;
column-span: 1;
layout: vertical;
background: #0d1117;
}

#bottom-panel {
row-span: 1;
column-span: 2;
height: 100%;
align: center middle;
background: $surface;
background: #0d1117;
border-top: solid #30363d;
}

.welcome-title {
/* Panel styles */
.panel {
border: none;
padding: 0 0 0 0;
height: 1fr;
overflow: auto;
}

.section-title {
text-style: bold;
text-align: center;
margin-bottom: 1;
color: $accent;
background: #21262d;
color: #89b4fa;
padding: 0 1;
width: 100%;
text-align: left;
height: 1;
}

.welcome-text {
text-align: center;
margin-bottom: 1;
/* Status panel */
#status-panel {
height: auto;
margin-bottom: 0;
border-bottom: solid #30363d;
}

.status-row {
height: 1;
margin-bottom: 0;
padding: 0 1;
}

.status-label {
width: 30%;
text-style: bold;
color: #c9d1d9;
}

.status-value {
width: 70%;
color: #7ee787;
}

/* Command panel */
#command-panel {
width: 100%;
height: 100%;
}

#command-row {
width: 100%;
height: auto;
background: #21262d;
}

#command-input {
width: 4fr;
background: #0d1117;
color: #c9d1d9;
border: none;
height: 1;
padding: 0 1;
margin: 0;
}

#run-command-btn {
width: 1fr;
background: #388bfd;
color: #ffffff;
height: 1;
margin: 0;
}

#command-output {
width: 100%;
height: 1fr;
background: #0d1117;
color: #8b949e;
padding: 1;
}

/* Trees and tables */
Tree {
padding: 0;
}

Tree > .tree--cursor {
background: #21262d;
color: #e6edf3;
}

Tree > .tree--highlight {
background: #388bfd;
color: #ffffff;
}

/* Rich log styling */
RichLog {
background: #0d1117;
color: #c9d1d9;
border: none;
padding: 0 1;
}

Button {
background: #21262d;
color: #c9d1d9;
border: none;
}

Button:hover {
background: #30363d;
}

Button.active {
background: #388bfd;
color: #ffffff;
}

/* Input field styling */
Input {
background: #0d1117;
color: #c9d1d9;
border: solid #30363d;
}

Input:focus {
border: solid #388bfd;
}
Empty file added src/gitx/git/handler.py
Empty file.
48 changes: 48 additions & 0 deletions src/gitx/widgets/branches_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from textual.widgets import Static, Tree
from textual.app import ComposeResult
from textual.containers import Vertical
from textual.widgets import Label
from rich.text import Text


class BranchesPanel(Static):
"""Panel that displays and manages branches."""

def compose(self) -> ComposeResult:
"""Compose the branches panel."""
yield Vertical(
Label("[bold]3-Local branches[/bold]", classes="section-title"),
Tree("Branches", id="branches-tree"),
classes="panel"
)

def on_mount(self) -> None:
"""Set up the tree when mounted."""
tree = self.query_one(Tree)

# Add local branches
current_branch = "feat/init-git-commands"
branches = [
"master",
"feat/init-git-commands",
"feat/init-gitx-app",
"doc/tutorial-update",
"feat/lint-ci",
"origin/feat/lint-ci",
"feat/ghactions-docs",
"feat/mkdocs-integrate"
]

# Add branches to tree with proper styling
for branch in branches:
node = tree.root.add_leaf(branch)
if branch == current_branch:
# Current branch in green with check mark
node.label.stylize("green bold")
node.label = Text("✓ ") + node.label
elif branch.startswith("origin/"):
# Remote branches in blue
node.label.stylize("blue")

# Expand the tree by default
tree.root.expand()
Loading