Skip to content

Commit

Permalink
fix: Use gitpython for ignored path checks, grey out ignored paths in…
Browse files Browse the repository at this point in the history
… directory trees, add "add to gitignore" "exclude in gitignore" commands
  • Loading branch information
tomlin7 committed Jun 23, 2024
1 parent 4e2ab8a commit 6ccc21a
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 91 deletions.
12 changes: 1 addition & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ python-tkdnd = "^0.2.1"
psutil = "^5.9.8"
google-generativeai = "^0.5.4"
sortedcontainers = "^2.4.0"
gitignore-parser = "^0.1.11"


[tool.poetry.group.dev.dependencies]
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ ptyprocess; platform_system != "Windows"
python-tkdnd
psutil
google-generativeai
gitignore_parser
21 changes: 15 additions & 6 deletions src/biscuit/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from tkinter import filedialog
from tkinter.messagebox import askyesnocancel

from src.biscuit.common.actionset import ActionSet

from .common import BaseGame
from .config import ConfigManager
from .editor import *
Expand Down Expand Up @@ -76,12 +78,6 @@ def open_directory(self, dir: str) -> None:
return

self.active_directory = dir
self.explorer.directory.change_path(dir)
self.set_title(os.path.basename(self.active_directory))

self.editorsmanager.delete_all_editors()
self.terminalmanager.delete_all_terminals()
self.terminalmanager.open_terminal()

try:
self.git.check_git()
Expand All @@ -90,6 +86,13 @@ def open_directory(self, dir: str) -> None:
self.logger.error(f"Checking git failed: {e}")
self.notifications.error("Checking git failed: see logs")

self.explorer.directory.change_path(dir)
self.set_title(os.path.basename(self.active_directory))

self.editorsmanager.delete_all_editors()
self.terminalmanager.delete_all_terminals()
self.terminalmanager.open_terminal()

self.event_generate("<<DirectoryChanged>>", data=dir)

def update_git(self) -> None:
Expand Down Expand Up @@ -217,6 +220,12 @@ def register_langserver(self, language: str, command: str) -> None:
def register_comment_prefix(self, language: str, prefix: str) -> None:
register_comment_prefix(language, prefix)

def register_actionset(self, actionset: ActionSet) -> None:
self.palette.register_actionset(actionset)

def register_command(self, name: str, command: typing.Callable) -> None:
self.settings.register_command(name, command)

def register_run_command(self, language: str, command: str) -> None:
self.execution_manager.register_command(language, command)

Expand Down
41 changes: 26 additions & 15 deletions src/biscuit/git/ignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import os
import typing

from gitignore_parser import parse_gitignore

if typing.TYPE_CHECKING:
from src.biscuit import App

Expand All @@ -15,25 +13,38 @@ class GitIgnore:
def __init__(self, master):
self.master: Git = master
self.base: App = master.base
self.match = None
self.path = ""
self.repo = self.master.repo

def load(self):
"""Load the .gitignore file."""

def load(self) -> None:
if not self.base.git_found:
return

self.repo = self.master.repo
self.path = os.path.join(self.base.active_directory, ".gitignore")
if os.path.exists(self.path):
self.match = parse_gitignore(self.path)
else:
self.match = None

def __contains__(self, path: str) -> bool:
"""Check if the given path is ignored by the .gitignore file."""
def check(self, path: list[str]) -> list:
"""returns list of ignored files"""

if not self.base.git_found:
return []

if not self.match:
return False
return self.repo.ignored(path)

def add(self, path: str) -> None:
"""Add the given path to the .gitignore file."""

if not self.base.git_found:
return

with open(self.path, "a") as f:
f.write(f"\n{path.replace("\\", "/")}")

def exclude(self, path: str) -> None:
"""Exclude the given path from the .gitignore file."""

if not self.base.git_found:
return

return self.match(path)
with open(self.path, "a") as f:
f.write(f"\n!{path.replace("\\", "/")}")
2 changes: 1 addition & 1 deletion src/biscuit/settings/theme/dark.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Dark(Theme):
name = "biscuit dark"

border = "#2A2A2A"
disabled = "#7A7A7A"
disabled = "#737373"

primary_background = "#181818"
primary_foreground = "#8B949E"
Expand Down
134 changes: 80 additions & 54 deletions src/biscuit/views/explorer/directorytree.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
import pathlib
import platform
import shutil
import subprocess
import threading
import tkinter as tk
from tkinter.messagebox import askyesno
from typing import Iterator

import pyperclip

Expand Down Expand Up @@ -55,14 +57,15 @@ def __init__(
"build",
]

s = os.sep
self.changes_ignore_dir_patterns = [
"*/.git/*",
"*/__pycache__/*",
"*/.pytest_cache/*",
"*/node_modules/*",
"*/debug/*",
"*/dist/*",
"*/build/*",
f"*{s}.git{s}*",
f"*{s}__pycache__{s}*",
f"*{s}.pytest_cache{s}*",
f"*{s}node_modules{s}*",
f"*{s}debug{s}*",
f"*{s}dist{s}*",
f"*{s}build{s}*",
]

self.ignore_exts = [".pyc"]
Expand Down Expand Up @@ -167,14 +170,20 @@ def get_all_files(self) -> list:

return files

def scandir(self, path: str) -> list:
"""Returns a list of entries in the given directory.
Heloper function for updating the treeview."""
def scandir(self, path: str) -> Iterator:
"""Scans the given directory and yields its contents."""

entries = []
for entry in os.scandir(path):
entries.append((entry.name, os.path.join(self.path, entry.path)))
return entries
isdir = os.path.isdir(entry.path)
data = (
entry.name,
os.path.join(self.path, entry.path),
isdir,
entry.path.replace("\\", "/")
+ ("/" if isdir else ""), # unix-like path
)

yield data

def update_path(self, path: str) -> None:
"""Updates the treeview with the contents of the given directory."""
Expand All @@ -184,57 +193,60 @@ def update_path(self, path: str) -> None:

node = self.nodes.get(os.path.abspath(path))
for i in self.tree.get_children(node):
self.tree.delete(i)
try:
self.tree.delete(i)
except:
pass

self.create_root(path, node)

def update_treeview(self, parent_path: str, parent="") -> None:
"""Updates the treeview with the contents of the given directory."""
"""Lazy loads the treeview with the contents of the given directory.
Initially this was done recursively, but it was changed to a lazy load
to improve performance. The treeview is updated only when the user
expands a directory node."""

if not os.path.exists(parent_path):
return

entries = self.scandir(parent_path)

# sort: directories first, then files (alphabetic order)
entries.sort(key=lambda x: (not os.path.isdir(x[1]), x[0]))
entries = sorted(self.scandir(parent_path), key=lambda x: (not x[2], x[0]))
ignored = self.git.ignore.check([i[3] for i in entries])

for name, path, isdir, unixlike in entries:
if isdir:
if name in self.hide_dirs:
continue

node = self.tree.insert(
parent or "",
"end",
text=f" {name}",
values=[path, "directory"],
# image="foldericon",
open=False,
tags="ignored" if unixlike in ignored else "",
)
self.nodes[os.path.abspath(path)] = node
self.tree.insert(node, "end", text="loading...", tags="ignored")

try:
for name, path in entries:
if os.path.isdir(path):
if name in self.hide_dirs:
continue

node = self.tree.insert(
parent,
"end",
text=f" {name}",
values=[path, "directory"],
image="foldericon",
open=False,
tags="ignored" if name in self.git.ignore else "",
)
self.nodes[os.path.abspath(path)] = node
self.tree.insert(node, "end", text="loading...", tags="ignored")

# recursive mode loading (not good for large projects)
# self.update_treeview(path, node)
else:
if name.split(".")[-1] in self.ignore_exts:
continue

# TODO check filetype and get matching icon, cases
node = self.tree.insert(
parent,
"end",
text=f" {name}",
values=[path, "file"],
image="document",
tags="ignored" if name in self.git.ignore else "",
)
self.nodes[os.path.abspath(path)] = node
except Exception as e:
self.base.logger.error(f"Error updating treeview: {e}")
# NOTE: recursive mode loading (not good for large projects)
# self.update_treeview(path, node)
else:
if name.split(".")[-1] in self.ignore_exts:
continue

# TODO check filetype and get matching icon, cases
node = self.tree.insert(
parent,
"end",
text=f" {name}",
values=[path, "file"],
image="document",
tags="ignored" if unixlike in ignored else "",
)
self.nodes[os.path.abspath(path)] = node

def selected_directory(self) -> str:
"""Returns the selected directory path, or the current path if no directory is selected."""
Expand Down Expand Up @@ -313,6 +325,20 @@ def attach_to_chat(self, *_) -> None:
if self.tree.is_file_selected():
self.base.ai.attach_file(path)

def add_to_gitignore(self, *_) -> None:
"""Adds the selected item to the .gitignore file."""

path = os.path.relpath(self.tree.selected_path(), self.path)
self.git.ignore.add((path + "/") if self.tree.is_directory_selected() else path)

def exclude_from_gitignore(self, *_) -> None:
"""Excludes the selected item from the .gitignore file."""

path = os.path.relpath(self.tree.selected_path(), self.path)
self.git.ignore.exclude(
(path + "/") if self.tree.is_directory_selected() else path
)

def reopen_editor(self, *_) -> None:
"""Reopens the selected file in the editor."""

Expand Down
2 changes: 2 additions & 0 deletions src/biscuit/views/explorer/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def __init__(self, master: DirectoryTree, *args, **kwargs) -> None:
)
self.add_separator()
self.add_command("Attach to bikkis...", self.master.attach_to_chat)
self.add_command("Add to .gitignore", self.master.add_to_gitignore)
self.add_command("Exclude from .gitignore", self.master.exclude_from_gitignore)
self.add_separator()
self.add_command("Rename...", lambda: self.base.palette.show("rename:"))
self.add_command("Delete", self.master.delete_item)
Expand Down
2 changes: 0 additions & 2 deletions src/biscuit/views/explorer/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,10 @@ def stop_watch(self) -> None:
def on_created(self, event) -> None:
self.master.update_path(os.path.dirname(event.src_path))
self.base.source_control.reload_tree()
print("created", event.src_path)

def on_deleted(self, event) -> None:
self.master.update_path(os.path.dirname(event.src_path))
self.base.source_control.reload_tree()
print("deleted", event.src_path)

def on_modified(self, event) -> None:
# self.master.update_path(os.path.dirname(event.src_path))
Expand Down

0 comments on commit 6ccc21a

Please sign in to comment.