Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Biscuit CLI #328

Merged
merged 8 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
151 changes: 75 additions & 76 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "biscuit"
version = "2.90.0"
version = "2.92.0"
description = "The uncompromising code editor"
authors = ["Billy <billydevbusiness@gmail.com>"]
license = "MIT"
Expand Down
5 changes: 3 additions & 2 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
__version__ = "2.90.0"
__version__ = "2.92.0"
__version_info__ = tuple([int(num) for num in __version__.split(".")])

import sys
from os.path import abspath, dirname, join

sys.path.append(abspath(join(dirname(__file__), ".")))

from .biscuit import *
from biscuit import *
from main import *
6 changes: 2 additions & 4 deletions src/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import sys
from cli import cli

from main import main

main(sys.argv)
cli()
14 changes: 8 additions & 6 deletions src/biscuit/editor/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ def get_editor(
base,
path: str = None,
exists: bool = True,
path2: str = None,
diff: bool = False,
language: str = None,
standalone: bool = False,
) -> TextEditor | DiffEditor | MDEditor | ImageViewer:
"""Get the suitable editor based on the path, exists, diff values passed.

Args:
base: The parent widget
path (str): The path of the file to be opened
exists (bool): Whether the file exists
path2 (str): The path of the file to be opened in diff, required if diff=True is passed
diff (bool): Whether the file is to be opened in diff editor
language (str): The language of the file

Expand All @@ -33,7 +36,7 @@ def get_editor(
The suitable editor based on the path, exists, diff values passed"""

if diff:
return DiffEditor(base, path, exists, language=language)
return DiffEditor(base, path, exists, path2, standalone=standalone)

if path and os.path.isfile(path):
if is_image(path):
Expand Down Expand Up @@ -68,7 +71,7 @@ def __init__(
path2: str = None,
diff: bool = False,
language: str = None,
darkmode=True,
standalone: bool = False,
config_file: str = None,
showpath: bool = True,
preview_file_callback=None,
Expand All @@ -86,8 +89,6 @@ def __init__(
diff (bool): Whether the file is to be opened in diff editor
language (str): Use the `Languages` enum provided (eg. Languages.PYTHON, Languages.TYPESCRIPT)
This is given priority while picking suitable highlighter. If not passed, guesses from file extension.
darkmode (str): Sets the editor theme to cupcake dark if True, or cupcake light by default
This is ignored if custom config_file path is passed
config_file (str): path to the custom config (TOML) file, uses theme defaults if not passed
showpath (bool): Whether to show the breadcrumbs for editor or not
preview_file_callback (function): called when files in breadcrumbs-pathview are single clicked. MUST take an argument (path)
Expand All @@ -100,16 +101,17 @@ def __init__(
self.exists = exists
self.path2 = path2
self.diff = diff
self.language = language
self.standalone = standalone
self.showpath = showpath
self.darkmode = darkmode
self.config_file = config_file
self.preview_file_callback = preview_file_callback
self.open_file_callback = open_file_callback

self.config(bg=self.base.theme.border)
self.grid_columnconfigure(0, weight=1)

self.content = get_editor(self, path, exists, diff, language)
self.content = get_editor(self, path, exists, path2, diff, language, standalone)
self.filename = os.path.basename(self.path) if path else None
if path and exists and self.showpath and not diff:
self.breadcrumbs = BreadCrumbs(self, path)
Expand Down
17 changes: 11 additions & 6 deletions src/biscuit/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,18 @@ def update_git(self) -> None:
self.statusbar.update_git_info()
self.source_control.refresh()

def clone_repo(self, url: str) -> None:
def clone_repo(self, url: str, new_window: bool = None) -> None:
path = filedialog.askdirectory()
if not path:
return

new_window = askyesnocancel(
"Open in new window or current",
"Do you want to open the cloned repository in a new window?",
)
if new_window is None:
return
new_window = askyesnocancel(
"Open in new window or current",
"Do you want to open the cloned repository in a new window?",
)
if new_window is None:
return

try:

Expand Down Expand Up @@ -180,8 +181,12 @@ def open_editor(self, path: str, exists: bool = True) -> Editor | BaseEditor:
return self.editorsmanager.open_editor(path, exists)

def open_diff(self, path: str, kind: str) -> None:
# TODO kind kwarg
self.editorsmanager.open_diff_editor(path, kind) # type: ignore

def diff_files(self, file1: str, file2: str) -> None:
self.editorsmanager.diff_files(file1, file2, standalone=True)

def open_settings(self, *_) -> None:
self.editorsmanager.add_editor(SettingsEditor(self.editorsmanager))

Expand Down
2 changes: 1 addition & 1 deletion src/biscuit/git/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .git import Git
from .git import *
from .releases import Releases
from .repo import GitRepo
81 changes: 77 additions & 4 deletions src/biscuit/git/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@ class DiffEditor(BaseEditor):
Standard difflib is used to get the diff of the file. The diff is shown in two panes,
LHS shows last commit and RHS shows the current state of the file."""

def __init__(self, master, path, kind, language=None, *args, **kwargs) -> None:
def __init__(
self,
master,
path: str,
kind: int = None,
path2: str = None,
standalone: bool = False,
language="",
*args,
**kwargs,
) -> None:
super().__init__(master, *args, **kwargs)
self.config(bg=self.base.theme.border)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
self.path = path
self.kind = kind
self.path2 = path2
self.editable = True
self.standalone = standalone

self.lhs_data = []
self.rhs_data = []
Expand All @@ -47,6 +59,7 @@ def __init__(self, master, path, kind, language=None, *args, **kwargs) -> None:
self.rhs = DiffPane(self, path, exists=False)
self.rhs.grid(row=0, column=1, sticky=tk.NSEW)

self.differ = difflib.Differ()
self.debugger = self.rhs.debugger

self.left = self.lhs.text
Expand Down Expand Up @@ -77,9 +90,10 @@ def __init__(self, master, path, kind, language=None, *args, **kwargs) -> None:
)
self.right.tag_config("addedword", background="green")

self.differ = difflib.Differ()

self.show_diff()
if path2:
self.run_show_diff()
else:
self.run_show_git_diff()

def on_scrollbar(self, *args) -> None:
self.left.yview(*args)
Expand All @@ -101,6 +115,65 @@ def run_show_diff(self) -> None:
def show_diff(self) -> None:
"""Show the diff of the file"""

with open(
(
self.path
if self.standalone
else os.path.join(self.base.active_directory, self.path)
),
"r",
) as f:
lhs_data = f.read()
with open(
(
self.path2
if self.standalone
else os.path.join(self.base.active_directory, self.path2)
),
"r",
) as f:
rhs_data = f.read()

lhs_lines = [line + "\n" for line in lhs_data.split("\n")]
rhs_lines = [line + "\n" for line in rhs_data.split("\n")]

self.diff = list(self.differ.compare(lhs_lines, rhs_lines))
for i, line in enumerate(self.diff):
marker = line[0]
content = line[2:]

match marker:
case " ":
# line is same in both
self.left.write(content)
self.right.write(content)

case "-":
# line is only on the left
self.lhs_last_line = int(float(self.left.index(tk.INSERT)))
self.left.write(content, "removal")

# TODO this check is done to make sure if this is a line with modifications
# and not a newly added line, but this is not done right.
self.right.insert_newline("removal")

case "+":
# line is only on the right
self.rhs_last_line = int(float(self.right.index(tk.INSERT)))
self.right.write(content, "addition")

# TODO this check is done to make sure if this is a line with modifications
# and not a newly added line, but this is not done right.
self.left.insert_newline("addition")

def run_show_git_diff(self) -> None:
"""Run the show_diff_git function in a separate thread"""

threading.Thread(target=self.show_git_diff).start()

def show_git_diff(self) -> None:
"""Show the diff of the file"""

try:
# case: deleted file
if not self.kind:
Expand Down
11 changes: 11 additions & 0 deletions src/biscuit/layout/editors/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ def open_diff_editor(self, path: str, exists: bool) -> None:

self.add_editor(Editor(self, path, exists, diff=True))

def diff_files(self, file1: str, file2: str, standalone: bool = False) -> None:
"""Diff two files.

Args:
file1 (str): The path of the first file.
file2 (str): The path of the second file."""

self.add_editor(
Editor(self, file1, True, file2, diff=True, standalone=standalone)
)

def open_game(self, id: str) -> None:
"""Open a new game editor with the given id.

Expand Down
13 changes: 13 additions & 0 deletions src/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Feature
A CLI application for Biscuit using Python click (suggestions for other CLI libs open).

### Commands
- `biscuit --help` - Show help for the Biscuit CLI
- `biscuit --version` - Show the version of Biscuit
- `biscuit --dev` - Start Biscuit in development mode
- `biscuit clone [URL]` - Clone a Biscuit repository
- `biscuit diff [FILE1] [FILE2]` - Show the differences between the local and remote Biscuit repositories

### Extension dev commands (not implemented)
- `biscuit extension create` - Create a new extension
- `biscuit extension test` - Test an extension
1 change: 1 addition & 0 deletions src/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .cli import cli
92 changes: 92 additions & 0 deletions src/cli/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from pathlib import Path
from typing import Callable

import click

from src import App, __version__, get_app_instance
from src.biscuit.git import URL
from src.cli import extensions


@click.group(invoke_without_command=True)
@click.version_option(__version__, "-v", "--version", message="Biscuit v%(version)s")
@click.help_option("-h", "--help")
@click.option("--dev", is_flag=True, help="Run in development mode")
def cli(path=None, dev=False):
"""Biscuit CLI"""

click.echo(f"Biscuit v{__version__} {'(dev) 🚧' if dev else '🚀'}")


@cli.result_callback()
def process_commands(processors, path=None, dev=False):
"""Process the commands"""

if path:
path = str(Path(path).resolve())
click.echo(f"Opening {path}")

app = get_app_instance(open_path=path)

if processors:
if isinstance(processors, list):
for processor in processors:
processor(app)
else:
processors(app)

app.run()


@cli.command()
@click.argument("url", type=str)
def clone(url) -> Callable[[App, str], None]:
"""Clone & open a git repository in Biscuit"""

if not url:
url = click.prompt("Git repository url", type=str)

click.echo(
f"Cloning repository from {'https://github.com/' if not URL.match(url) else ''}{url}"
)
return lambda app, url=url: app.clone_repo(url, new_window=False)


@cli.command()
@click.argument(
"file1",
type=click.Path(
exists=True,
dir_okay=False,
resolve_path=True,
),
required=False,
)
@click.argument(
"file2",
type=click.Path(
exists=True,
dir_okay=False,
resolve_path=True,
),
required=False,
)
def diff(file1=None, file2=None) -> Callable[[App, str], None]:
"""Diff two files"""

if not file1:
file1 = click.prompt("path/to/file", type=str)
if not file2:
file2 = click.prompt("path/to/second/file", type=str)

return lambda app, file1=file1, file2=file2: app.diff_files(file1, file2)


def setup():
extensions.setup(cli)


setup()

if __name__ == "__main__":
cli()
Loading
Loading