Skip to content

Commit 3514b05

Browse files
authored
feat: Biscuit CLI (diff, git, file navigation, extension management commands fully implemented) (merge #328, nightly)
2 parents 5223fe0 + f9252a3 commit 3514b05

38 files changed

+1166
-395
lines changed

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"editor.formatOnSave": true
1111
},
1212
"editor.codeActionsOnSave": {
13-
"source.organizeImports": "explicit"
13+
"source.organizeImports": "always"
1414
},
1515
"python.analysis.autoImportCompletions": true,
16-
}
16+
}

poetry.lock

Lines changed: 75 additions & 76 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "biscuit"
3-
version = "2.90.0"
3+
version = "2.92.0"
44
description = "The uncompromising code editor"
55
authors = ["Billy <billydevbusiness@gmail.com>"]
66
license = "MIT"

resources/fonts/NOTICE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generated from https://github.com/microsoft/vscode-codicons
File renamed without changes.

src/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
__version__ = "2.90.0"
1+
__version__ = "2.92.0"
22
__version_info__ = tuple([int(num) for num in __version__.split(".")])
33

44
import sys
55
from os.path import abspath, dirname, join
66

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

9-
from .biscuit import *
9+
from biscuit import *
10+
from main import *

src/__main__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import sys
1+
from cli import run
22

3-
from main import main
4-
5-
main(sys.argv)
3+
run()

src/biscuit/api/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# The Extension API
2+
The Biscuit extension API allows you to extend the functionality of Biscuit by adding new commands, completions, and more.
3+
4+
## Commands
5+
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.
6+
7+
### Creating a Command
8+
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:
9+
10+
```py
11+
class Extension:
12+
def __init__(self, api):
13+
self.api = api
14+
15+
self.api.commands.registerCommand('example command', self.example_command)
16+
17+
def example_command(self, *_):
18+
self.api.notifications.info('Hello, world!')
19+
```
20+
21+
In this example, we define a new command called `example command` that displays a notification when run.
22+
23+
## Logging
24+
The Biscuit extension API provides a logging API that allows you to log messages to the Biscuit output panel.
25+
26+
### Logging a Message
27+
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:
28+
29+
```py
30+
class Extension:
31+
def __init__(self, api):
32+
self.api = api
33+
34+
def run(self):
35+
self.api.logger.trace('This is a trace message!')
36+
self.api.logger.info('Hello, world!')
37+
self.api.logger.warning('This is a warning!')
38+
self.api.logger.error('An error occurred!')
39+
```
40+
41+
In this example, we log a trace message, an info message, a warning message, and an error message.
42+
43+
## Notifications
44+
The Biscuit extension API provides a notifications API that allows you to display notifications to the user.
45+
46+
### Displaying a Notification
47+
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:
48+
49+
```py
50+
class Extension:
51+
def __init__(self, api):
52+
self.api = api
53+
54+
def run(self):
55+
self.api.notifications.info('Hello, world!')
56+
self.api.notifications.warning('This is a warning!')
57+
self.api.notifications.error('An error occurred!')
58+
self.api.notifications.info('Info!')
59+
```
60+
61+
In this example, we display an info notification, a warning notification, an error notification, and a success notification.
62+
63+
#TODO document full API

src/biscuit/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __init__(self, appdir: str = "", dir: str = "", *args, **kwargs) -> None:
5454

5555
self.setup()
5656
self.late_setup()
57-
self.initialize_editor(dir)
57+
self.initialize_app(dir)
5858

5959
def run(self) -> None:
6060
"""Start the main loop of the app."""
@@ -71,7 +71,7 @@ def setup(self) -> None:
7171
self.setup_configs()
7272
self.initialize_tk()
7373

74-
def initialize_editor(self, dir: str) -> None:
74+
def initialize_app(self, dir: str) -> None:
7575
"""Initialize the editor.
7676
7777
Args:

src/biscuit/commands.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
from __future__ import annotations
22

33
import os
4-
import typing
5-
import webbrowser as web
6-
from tkinter import messagebox
7-
8-
if typing.TYPE_CHECKING:
9-
from .. import App
10-
114
import platform
125
import tkinter as tk
136
import tkinter.filedialog as filedialog
7+
import typing
8+
import webbrowser as web
9+
from tkinter import messagebox
1410
from tkinter.filedialog import asksaveasfilename
1511

1612
from src.biscuit.common.classdrill import *
1713

14+
if typing.TYPE_CHECKING:
15+
from .. import App
16+
1817

1918
class Commands:
2019
"""Commands that can be triggered by the user.
@@ -27,7 +26,7 @@ class Commands:
2726
"""
2827

2928
def __init__(self, base: App) -> None:
30-
self.base = base
29+
self.base: App = base
3130
self.count = 1
3231
self.maximized = False
3332
self.minimized = False
@@ -249,7 +248,7 @@ def rename_symbol(self, *_) -> None:
249248
editor.content.text.request_rename()
250249

251250
def restart_extension_server(self, *_) -> None:
252-
self.base.extensions.restart_server()
251+
self.base.extensions_manager.restart_server()
253252

254253
def show_explorer(self, *_) -> None:
255254
self.base.drawer.show_explorer()

src/biscuit/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,12 @@ def setup_path(self, appdir: str) -> None:
9999
except Exception as e:
100100
print(f"Extensions failed: {e}")
101101

102-
def setup_api(self):
102+
def setup_extensions(self):
103103
# sets up the extension API & loads extensions
104104
self.api = ExtensionsAPI(self)
105105
self.extensions_manager = ExtensionManager(self)
106+
self.extensions_view.results.late_setup()
107+
self.extensions_view.initialize()
106108

107109
def set_tab_spaces(self, spaces: int) -> None:
108110
self.tab_spaces = spaces

src/biscuit/editor/editor.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@
1414

1515
def get_editor(
1616
base,
17-
path: str = None,
18-
exists: bool = True,
19-
diff: bool = False,
20-
language: str = None,
17+
path="",
18+
exists=True,
19+
path2="",
20+
diff=False,
21+
language="",
22+
load_file=True,
23+
standalone=False,
2124
) -> TextEditor | DiffEditor | MDEditor | ImageViewer:
2225
"""Get the suitable editor based on the path, exists, diff values passed.
2326
2427
Args:
2528
base: The parent widget
2629
path (str): The path of the file to be opened
2730
exists (bool): Whether the file exists
31+
path2 (str): The path of the file to be opened in diff, required if diff=True is passed
2832
diff (bool): Whether the file is to be opened in diff editor
2933
language (str): The language of the file
3034
@@ -33,7 +37,7 @@ def get_editor(
3337
The suitable editor based on the path, exists, diff values passed"""
3438

3539
if diff:
36-
return DiffEditor(base, path, exists, language=language)
40+
return DiffEditor(base, path, exists, path2, standalone=standalone)
3741

3842
if path and os.path.isfile(path):
3943
if is_image(path):
@@ -45,9 +49,9 @@ def get_editor(
4549
if path.endswith(".html") or path.endswith(".htm"):
4650
return HTMLEditor(base, path, exists=exists)
4751

48-
return TextEditor(base, path, exists, language=language)
52+
return TextEditor(base, path, exists, language=language, load_file=load_file)
4953

50-
return TextEditor(base, exists=exists, language=language)
54+
return TextEditor(base, exists=exists, language=language, load_file=False)
5155

5256

5357
class Editor(BaseEditor):
@@ -68,7 +72,8 @@ def __init__(
6872
path2: str = None,
6973
diff: bool = False,
7074
language: str = None,
71-
darkmode=True,
75+
load_file: bool = True,
76+
standalone: bool = False,
7277
config_file: str = None,
7378
showpath: bool = True,
7479
preview_file_callback=None,
@@ -86,8 +91,6 @@ def __init__(
8691
diff (bool): Whether the file is to be opened in diff editor
8792
language (str): Use the `Languages` enum provided (eg. Languages.PYTHON, Languages.TYPESCRIPT)
8893
This is given priority while picking suitable highlighter. If not passed, guesses from file extension.
89-
darkmode (str): Sets the editor theme to cupcake dark if True, or cupcake light by default
90-
This is ignored if custom config_file path is passed
9194
config_file (str): path to the custom config (TOML) file, uses theme defaults if not passed
9295
showpath (bool): Whether to show the breadcrumbs for editor or not
9396
preview_file_callback (function): called when files in breadcrumbs-pathview are single clicked. MUST take an argument (path)
@@ -100,16 +103,26 @@ def __init__(
100103
self.exists = exists
101104
self.path2 = path2
102105
self.diff = diff
106+
self.language = language
107+
self.standalone = standalone
103108
self.showpath = showpath
104-
self.darkmode = darkmode
105109
self.config_file = config_file
106110
self.preview_file_callback = preview_file_callback
107111
self.open_file_callback = open_file_callback
108112

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

112-
self.content = get_editor(self, path, exists, diff, language)
116+
self.content = get_editor(
117+
self,
118+
path,
119+
exists,
120+
path2,
121+
diff,
122+
language,
123+
load_file=load_file,
124+
standalone=standalone,
125+
)
113126
self.filename = os.path.basename(self.path) if path else None
114127
if path and exists and self.showpath and not diff:
115128
self.breadcrumbs = BreadCrumbs(self, path)

src/biscuit/editor/editorbase.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import tkinter as tk
24

35
import tkinterDnD as dnd

src/biscuit/editor/text/editor.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,17 @@ def __init__(
2323
language=None,
2424
minimalist=False,
2525
standalone=False,
26+
load_file=True,
2627
*args,
2728
**kwargs,
2829
) -> None:
2930
super().__init__(master, path, exists, *args, **kwargs)
3031
self.font: Font = self.base.settings.font
32+
self.path = path
33+
self.exists = exists
3134
self.standalone = standalone
3235
self.minimalist = minimalist or self.standalone
3336
self.language = language
34-
self.exists = exists
3537
self.editable = True
3638
self.run_command_value = None
3739
self.debugger = None
@@ -65,7 +67,9 @@ def __init__(
6567
self.language = self.text.language
6668

6769
if self.exists:
68-
self.text.load_file()
70+
if load_file:
71+
self.text.load_file()
72+
6973
self.text.update_idletasks()
7074

7175
if not self.standalone:
@@ -140,7 +144,9 @@ def __getattr__(self, name):
140144

141145
def file_loaded(self):
142146
self.recalculate_content_hash()
147+
print(f"File opened {self.path}")
143148
self.event_generate("<<FileLoaded>>", when="tail")
149+
self.text.event_generate("<<FileLoaded>>", when="tail")
144150

145151
def recalculate_content_hash(self):
146152
"""Recalculate the hash of the editor content"""

src/biscuit/editor/text/highlighter.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ def __init__(self, text: Text, language: str = None, *args, **kwargs) -> None:
5555
except:
5656
self.lexer = None
5757
self.text.language = "Plain Text"
58-
if self.text.exists:
59-
print("Unrecognized file type opened")
6058

6159
self.tag_colors = self.base.theme.syntax
6260
self.setup_highlight_tags()
@@ -77,8 +75,6 @@ def detect_language(self) -> None:
7775
except:
7876
self.lexer = None
7977
self.text.language = "Plain Text"
80-
if self.text.exists:
81-
print("Unrecognized file type opened")
8278

8379
def change_language(self, language: str) -> None:
8480
"""Change the language of the highlighter

src/biscuit/editor/text/text.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ def write_with_buffer():
955955

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

958-
def read_file(self, file):
958+
def read_file(self, file: typing.TextIO):
959959
while True:
960960
try:
961961
chunk = file.read(self.buffer_size)
@@ -974,9 +974,13 @@ def process_queue(self, eol: str = None):
974974
while True:
975975
chunk = self.queue.get_nowait()
976976
if chunk is None:
977+
# Finished loading file -- reached EOF 🚧
977978
try:
978979
self.master.on_change()
979980
self.master.on_scroll()
981+
self.update_idletasks()
982+
self.master.file_loaded()
983+
self.focus_set()
980984
except Exception:
981985
pass
982986
break
@@ -994,9 +998,9 @@ def process_queue(self, eol: str = None):
994998
# If the queue is empty, schedule the next check after a short delay
995999
self.master.after(100, self.process_queue)
9961000

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

999-
def custom_get(self, start, end):
10001004
content = self.get(start, end)
10011005
tag_ranges = self.tag_ranges("ignore_tag")
10021006

0 commit comments

Comments
 (0)