Skip to content

Commit

Permalink
feat: Biscuit CLI (diff, git, file navigation, extension management c…
Browse files Browse the repository at this point in the history
…ommands fully implemented) (merge #328, nightly)
  • Loading branch information
tomlin7 authored Jun 12, 2024
2 parents 5223fe0 + f9252a3 commit 3514b05
Show file tree
Hide file tree
Showing 38 changed files with 1,166 additions and 395 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"editor.formatOnSave": true
},
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
"source.organizeImports": "always"
},
"python.analysis.autoImportCompletions": true,
}
}
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 <[email protected]>"]
license = "MIT"
Expand Down
1 change: 1 addition & 0 deletions resources/fonts/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generated from https://github.com/microsoft/vscode-codicons
File renamed without changes
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 run

from main import main

main(sys.argv)
run()
63 changes: 63 additions & 0 deletions src/biscuit/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# The Extension API
The Biscuit extension API allows you to extend the functionality of Biscuit by adding new commands, completions, and more.

## Commands
Commands are the primary way to extend Biscuit. They allow you to add new functionality to Biscuit by defining a new command that can be run from the command palette or from the terminal.

### Creating a Command
To create a new command, you need to use the `biscuit.commands.registerCommand` function. This function takes two arguments: the name of the command and a function callback that will be called when the command is run:

```py
class Extension:
def __init__(self, api):
self.api = api

self.api.commands.registerCommand('example command', self.example_command)

def example_command(self, *_):
self.api.notifications.info('Hello, world!')
```

In this example, we define a new command called `example command` that displays a notification when run.

## Logging
The Biscuit extension API provides a logging API that allows you to log messages to the Biscuit output panel.

### Logging a Message
To log a message, you can use the `biscuit.logger.info`, `biscuit.logger.error`, `biscuit.logger.warning`, `biscuit.logger.trace` methods. This function takes a message and an optional log level:

```py
class Extension:
def __init__(self, api):
self.api = api

def run(self):
self.api.logger.trace('This is a trace message!')
self.api.logger.info('Hello, world!')
self.api.logger.warning('This is a warning!')
self.api.logger.error('An error occurred!')
```

In this example, we log a trace message, an info message, a warning message, and an error message.

## Notifications
The Biscuit extension API provides a notifications API that allows you to display notifications to the user.

### Displaying a Notification
To display a notification, you can use the `biscuit.notifications.info`, `biscuit.notifications.error`, `biscuit.notifications.warning`, `biscuit.notifications.info` methods. This function takes a message and an optional title:

```py
class Extension:
def __init__(self, api):
self.api = api

def run(self):
self.api.notifications.info('Hello, world!')
self.api.notifications.warning('This is a warning!')
self.api.notifications.error('An error occurred!')
self.api.notifications.info('Info!')
```

In this example, we display an info notification, a warning notification, an error notification, and a success notification.

#TODO document full API
4 changes: 2 additions & 2 deletions src/biscuit/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__(self, appdir: str = "", dir: str = "", *args, **kwargs) -> None:

self.setup()
self.late_setup()
self.initialize_editor(dir)
self.initialize_app(dir)

def run(self) -> None:
"""Start the main loop of the app."""
Expand All @@ -71,7 +71,7 @@ def setup(self) -> None:
self.setup_configs()
self.initialize_tk()

def initialize_editor(self, dir: str) -> None:
def initialize_app(self, dir: str) -> None:
"""Initialize the editor.
Args:
Expand Down
17 changes: 8 additions & 9 deletions src/biscuit/commands.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
from __future__ import annotations

import os
import typing
import webbrowser as web
from tkinter import messagebox

if typing.TYPE_CHECKING:
from .. import App

import platform
import tkinter as tk
import tkinter.filedialog as filedialog
import typing
import webbrowser as web
from tkinter import messagebox
from tkinter.filedialog import asksaveasfilename

from src.biscuit.common.classdrill import *

if typing.TYPE_CHECKING:
from .. import App


class Commands:
"""Commands that can be triggered by the user.
Expand All @@ -27,7 +26,7 @@ class Commands:
"""

def __init__(self, base: App) -> None:
self.base = base
self.base: App = base
self.count = 1
self.maximized = False
self.minimized = False
Expand Down Expand Up @@ -249,7 +248,7 @@ def rename_symbol(self, *_) -> None:
editor.content.text.request_rename()

def restart_extension_server(self, *_) -> None:
self.base.extensions.restart_server()
self.base.extensions_manager.restart_server()

def show_explorer(self, *_) -> None:
self.base.drawer.show_explorer()
Expand Down
4 changes: 3 additions & 1 deletion src/biscuit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,12 @@ def setup_path(self, appdir: str) -> None:
except Exception as e:
print(f"Extensions failed: {e}")

def setup_api(self):
def setup_extensions(self):
# sets up the extension API & loads extensions
self.api = ExtensionsAPI(self)
self.extensions_manager = ExtensionManager(self)
self.extensions_view.results.late_setup()
self.extensions_view.initialize()

def set_tab_spaces(self, spaces: int) -> None:
self.tab_spaces = spaces
Expand Down
37 changes: 25 additions & 12 deletions src/biscuit/editor/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@

def get_editor(
base,
path: str = None,
exists: bool = True,
diff: bool = False,
language: str = None,
path="",
exists=True,
path2="",
diff=False,
language="",
load_file=True,
standalone=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 +37,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 All @@ -45,9 +49,9 @@ def get_editor(
if path.endswith(".html") or path.endswith(".htm"):
return HTMLEditor(base, path, exists=exists)

return TextEditor(base, path, exists, language=language)
return TextEditor(base, path, exists, language=language, load_file=load_file)

return TextEditor(base, exists=exists, language=language)
return TextEditor(base, exists=exists, language=language, load_file=False)


class Editor(BaseEditor):
Expand All @@ -68,7 +72,8 @@ def __init__(
path2: str = None,
diff: bool = False,
language: str = None,
darkmode=True,
load_file: bool = True,
standalone: bool = False,
config_file: str = None,
showpath: bool = True,
preview_file_callback=None,
Expand All @@ -86,8 +91,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 +103,26 @@ 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,
load_file=load_file,
standalone=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
2 changes: 2 additions & 0 deletions src/biscuit/editor/editorbase.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import tkinter as tk

import tkinterDnD as dnd
Expand Down
10 changes: 8 additions & 2 deletions src/biscuit/editor/text/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ def __init__(
language=None,
minimalist=False,
standalone=False,
load_file=True,
*args,
**kwargs,
) -> None:
super().__init__(master, path, exists, *args, **kwargs)
self.font: Font = self.base.settings.font
self.path = path
self.exists = exists
self.standalone = standalone
self.minimalist = minimalist or self.standalone
self.language = language
self.exists = exists
self.editable = True
self.run_command_value = None
self.debugger = None
Expand Down Expand Up @@ -65,7 +67,9 @@ def __init__(
self.language = self.text.language

if self.exists:
self.text.load_file()
if load_file:
self.text.load_file()

self.text.update_idletasks()

if not self.standalone:
Expand Down Expand Up @@ -140,7 +144,9 @@ def __getattr__(self, name):

def file_loaded(self):
self.recalculate_content_hash()
print(f"File opened {self.path}")
self.event_generate("<<FileLoaded>>", when="tail")
self.text.event_generate("<<FileLoaded>>", when="tail")

def recalculate_content_hash(self):
"""Recalculate the hash of the editor content"""
Expand Down
4 changes: 0 additions & 4 deletions src/biscuit/editor/text/highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ def __init__(self, text: Text, language: str = None, *args, **kwargs) -> None:
except:
self.lexer = None
self.text.language = "Plain Text"
if self.text.exists:
print("Unrecognized file type opened")

self.tag_colors = self.base.theme.syntax
self.setup_highlight_tags()
Expand All @@ -77,8 +75,6 @@ def detect_language(self) -> None:
except:
self.lexer = None
self.text.language = "Plain Text"
if self.text.exists:
print("Unrecognized file type opened")

def change_language(self, language: str) -> None:
"""Change the language of the highlighter
Expand Down
10 changes: 7 additions & 3 deletions src/biscuit/editor/text/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ def write_with_buffer():

threading.Thread(target=write_with_buffer, daemon=True).start()

def read_file(self, file):
def read_file(self, file: typing.TextIO):
while True:
try:
chunk = file.read(self.buffer_size)
Expand All @@ -974,9 +974,13 @@ def process_queue(self, eol: str = None):
while True:
chunk = self.queue.get_nowait()
if chunk is None:
# Finished loading file -- reached EOF 🚧
try:
self.master.on_change()
self.master.on_scroll()
self.update_idletasks()
self.master.file_loaded()
self.focus_set()
except Exception:
pass
break
Expand All @@ -994,9 +998,9 @@ def process_queue(self, eol: str = None):
# If the queue is empty, schedule the next check after a short delay
self.master.after(100, self.process_queue)

self.master.file_loaded()
def custom_get(self, start: str, end: str) -> str:
"""Ignore the text that is tagged with 'ignore_tag' and return the rest of the text."""

def custom_get(self, start, end):
content = self.get(start, end)
tag_ranges = self.tag_ranges("ignore_tag")

Expand Down
Loading

0 comments on commit 3514b05

Please sign in to comment.