diff --git a/Makefile b/Makefile
index 33ac46f..99b1a38 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
APP_NAME := graptos
APP_ID := io.github.graptos.Editor
-VERSION := 0.23.68
+VERSION := 0.23.73
PREFIX ?= /usr/local
CC ?= cc
BUILD_DIR := build
@@ -44,6 +44,7 @@ SOURCES := \
$(SRC_DIR)/project_init.c \
$(SRC_DIR)/project_init_ui.c \
$(SRC_DIR)/project_search.c \
+ $(SRC_DIR)/plugin.c \
$(SRC_DIR)/git.c \
$(SRC_DIR)/codex_protocol.c \
$(SRC_DIR)/codex_client.c \
@@ -89,7 +90,7 @@ else
$(error GtkSourceView 5 is required but not detected. Install gtksourceview-5 development files.)
endif
-GTK_PKG := gtk4 gtksourceview-5 json-glib-1.0 vte-2.91-gtk4
+GTK_PKG := gtk4 gtksourceview-5 json-glib-1.0 vte-2.91-gtk4 gmodule-2.0
GTK_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(GTK_PKG))
GTK_SYSTEM_CFLAGS := $(foreach flag,$(GTK_CFLAGS),$(if $(filter -I%,$(flag)),-isystem $(patsubst -I%,%,$(flag)),$(flag)))
GTK_LIBS := $(shell $(PKG_CONFIG) --libs $(GTK_PKG))
@@ -129,8 +130,8 @@ check: $(VERSION_HEADER)
test: $(BUILD_DIR)/unit_tests
$<
-$(BUILD_DIR)/unit_tests: $(TEST_DIR)/unit_tests.c $(SRC_DIR)/codex_protocol.c $(SRC_DIR)/syntax_diagnostics.c $(SRC_DIR)/project_init.c $(SRC_DIR)/formatter.c $(SRC_DIR)/formatter_lexer.c $(SRC_DIR)/formatter_layout.c $(SRC_DIR)/formatter_spacing.c $(SRC_DIR)/formatter_scope.c $(SRC_DIR)/syntax.c $(SRC_DIR)/editor_notes.c | $(BUILD_DIR)
- $(CC) $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(GTK_SYSTEM_CFLAGS) -std=c11 $^ $(GTK_LIBS) -o $@
+$(BUILD_DIR)/unit_tests: $(TEST_DIR)/unit_tests.c $(SRC_DIR)/codex_protocol.c $(SRC_DIR)/syntax_diagnostics.c $(SRC_DIR)/project_init.c $(SRC_DIR)/formatter.c $(SRC_DIR)/formatter_lexer.c $(SRC_DIR)/formatter_layout.c $(SRC_DIR)/formatter_spacing.c $(SRC_DIR)/formatter_scope.c $(SRC_DIR)/syntax.c $(SRC_DIR)/editor_notes.c $(SRC_DIR)/plugin.c | $(BUILD_DIR)
+ $(CC) $(CPPFLAGS) -DGRAPTOS_PLUGIN_NO_UI $(CFLAGS) $(WARNINGS) $(GTK_SYSTEM_CFLAGS) -std=c11 $^ $(GTK_LIBS) -o $@
smoke-test: $(BUILD_DIR)/smoke_window
$<
@@ -160,6 +161,10 @@ install: $(BUILD_DIR)/$(APP_NAME)
cp -R data/fonts/Inconsolata/. $(DESTDIR)$(DATADIR)/fonts/Inconsolata/
install -d $(DESTDIR)$(DATADIR)/project-templates
cp -R data/project-templates/. $(DESTDIR)$(DATADIR)/project-templates/
+ @if [ -d data/plugins ]; then \
+ install -d $(DESTDIR)$(DATADIR)/plugins; \
+ cp -R data/plugins/. $(DESTDIR)$(DATADIR)/plugins/; \
+ fi
install -Dm644 data/$(APP_ID).desktop $(DESTDIR)$(PREFIX)/share/applications/$(APP_ID).desktop
uninstall:
diff --git a/README.md b/README.md
index 0eff2cf..28c35b3 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,18 @@
-
+
+
+

+
+### γραπτός: Grap-Tos; adj, written or inscribed
-# Graptoς _Previously Cleaf_
+[](https://github.com/Logic-gate/Graptos/actions/workflows/c-cpp.yml)
+
+
+
+
+
+
+
-γραπτός: Grap-Tos; adj, written or inscribed
Graptoς is a small text editor for Linux, written in pure C with GTK 4 and
GtkSourceView 5. It is still under active development and is not ready for
@@ -477,6 +487,15 @@ icon: LOG
index: false
```
+## Plugins
+
+Graptoς plugins are local folders with a `plugin.yaml` manifest. The plugin
+layer is the boundary between Graptoς internals and extensions: plugins declare
+what they contribute, and Graptoς decides how those contributions are loaded and
+executed.
+
+See [docs/PLUGINS.md](docs/PLUGINS.md) for more information.
+
### AI Policy
AI contributions are based on [AI USAGE POLICY VERSION 1](https://github.com/Logic-gate/AI-USAGE-POLICY/blob/main/POLICY/VERSION_1.0.0.md) [docs/AI_USAGE_POLICY.md](docs/AI_USAGE_POLICY.md).
diff --git a/data/logos/cleaf-logo-dark.png b/data/logos/cleaf-logo-dark.png
deleted file mode 100644
index db41a06..0000000
Binary files a/data/logos/cleaf-logo-dark.png and /dev/null differ
diff --git a/data/logos/cleaf-logo-light.png b/data/logos/cleaf-logo-light.png
deleted file mode 100644
index b682f05..0000000
Binary files a/data/logos/cleaf-logo-light.png and /dev/null differ
diff --git a/data/logos/graptos-logo-dark.png b/data/logos/graptos-logo-dark.png
index db41a06..a3bab2c 100644
Binary files a/data/logos/graptos-logo-dark.png and b/data/logos/graptos-logo-dark.png differ
diff --git a/data/logos/graptos-logo-light.png b/data/logos/graptos-logo-light.png
index b682f05..a3bab2c 100644
Binary files a/data/logos/graptos-logo-light.png and b/data/logos/graptos-logo-light.png differ
diff --git a/data/plugins/full-demo/plugin.yaml b/data/plugins/full-demo/plugin.yaml
new file mode 100644
index 0000000..da4aeec
--- /dev/null
+++ b/data/plugins/full-demo/plugin.yaml
@@ -0,0 +1,40 @@
+id: full-demo
+name: Full Demo Plugin
+version: 1.0
+description: Shows every current permission in one manifest.
+graptos_api_version: 1
+enabled: false
+native: lib/libgraptos_full_demo.so
+
+permissions:
+ - editor.read
+ - editor.write
+ - project.read
+ - project.write
+ - ui
+ - syntax
+ - theme
+ - template
+ - git
+ - terminal
+ - command
+ - native
+
+contributes:
+ syntaxes:
+ - syntax
+ themes:
+ - themes
+ templates:
+ - templates
+ commands:
+ - show-plugin-info
+ - line-word-count
+ - insert-section-banner
+ - project-summary
+ - git-blame-line
+ - run-current-file-in-terminal
+ - run-external-formatter
+ - native-demo-command
+ menus:
+ - editor-line:Full Demo Info:show-plugin-info
diff --git a/data/plugins/full-demo/syntax/full-demo-log.yaml b/data/plugins/full-demo/syntax/full-demo-log.yaml
new file mode 100644
index 0000000..82047ff
--- /dev/null
+++ b/data/plugins/full-demo/syntax/full-demo-log.yaml
@@ -0,0 +1,70 @@
+name: Full Demo Log
+extensions: [.fulldemo]
+line_comment: "#"
+auto_indent: false
+indent_openers: []
+indent_closers: []
+close_pairs: ["(=>)", "{=>}", "[=>]"]
+icon: demo
+filenames: []
+index: false
+import_style: none
+lsp_command:
+lsp_language_id:
+lsp_args: []
+import_keywords: []
+import_from_keywords: []
+import_member_keywords: []
+import_roots: []
+import_env: []
+import_extensions: []
+import_member_files: []
+import_strip_extensions: true
+import_dot_modules: false
+import_static_modules: []
+completions: [INFO, WARN, ERROR, TODO]
+line_close_pairs: []
+statement_required_enders: []
+statement_exempt_prefixes: ["#", "INFO", "WARN", "ERROR", "TODO"]
+statement_exempt_suffixes: []
+formatting:
+ enabled: false
+ scope: selection_or_block
+ profile: none
+ block_style: none
+ statement_style: newline
+ brace_style: attach
+ one_statement_per_line: false
+ space_before_block_opener: false
+ space_after_comma: true
+ space_after_control_keyword: true
+ space_around_binary_operators: true
+ space_around_logical_operators: true
+ logical_operators: []
+ binary_operators: []
+ unary_prefix_operators: []
+ protected_scopes: [comment]
+ pointer_alignment: name
+ continuation_indent: 2
+ max_column: 100
+ wrap_after_comma: false
+ case_indent: 0
+ preserve_blank_lines: true
+ max_blank_lines: 1
+ collapse_empty_blocks: false
+rules:
+ - name: comment
+ scope: comment
+ pattern: "#.*$"
+ color: "#6A9955"
+ - name: error
+ pattern: "^ERROR\\b.*$"
+ color: "#FF6B6B"
+ bold: true
+ - name: warn
+ pattern: "^WARN\\b.*$"
+ color: "#F9C74F"
+ bold: true
+ - name: info
+ pattern: "^INFO\\b.*$"
+ color: "#57CC99"
diff --git a/data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo b/data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo
new file mode 100644
index 0000000..7feba54
--- /dev/null
+++ b/data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo
@@ -0,0 +1,4 @@
+# Full demo plugin project
+INFO Owner: ${owner}
+WARN This is a showcase template
+ERROR This line is only here to demonstrate highlighting
diff --git a/data/plugins/full-demo/templates/full-demo-project/template.yaml b/data/plugins/full-demo/templates/full-demo-project/template.yaml
new file mode 100644
index 0000000..70a3684
--- /dev/null
+++ b/data/plugins/full-demo/templates/full-demo-project/template.yaml
@@ -0,0 +1,19 @@
+version: 1
+id: full-demo-project
+name: Full Demo Project
+description: Project template contributed by the full demo plugin.
+language: Full Demo Log
+
+variables:
+ owner:
+ label: Owner
+ type: text
+ default: ${project_name}
+
+files:
+ - path: demo.fulldemo
+ template: files/demo.fulldemo
+
+open:
+ project: .
+ file: demo.fulldemo
diff --git a/data/plugins/full-demo/themes/full-demo.css b/data/plugins/full-demo/themes/full-demo.css
new file mode 100644
index 0000000..100952f
--- /dev/null
+++ b/data/plugins/full-demo/themes/full-demo.css
@@ -0,0 +1,6 @@
+/* Full demo theme contribution. */
+@define-color graptos_editor_bg #101014;
+@define-color graptos_editor_fg #f1f5f9;
+@define-color graptos_tab_active_border #f9c74f;
+@define-color graptos_dialog_bg #15151c;
+@define-color graptos_dialog_fg #f1f5f9;
diff --git a/data/plugins/permission-command/plugin.yaml b/data/plugins/permission-command/plugin.yaml
new file mode 100644
index 0000000..507a5b1
--- /dev/null
+++ b/data/plugins/permission-command/plugin.yaml
@@ -0,0 +1,13 @@
+id: permission-command
+name: External Command Demo
+version: 1.0
+description: Demonstrates the command permission shape for future external process commands.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - command
+
+contributes:
+ commands:
+ - run-external-formatter
diff --git a/data/plugins/permission-editor-read/plugin.yaml b/data/plugins/permission-editor-read/plugin.yaml
new file mode 100644
index 0000000..4e60b3f
--- /dev/null
+++ b/data/plugins/permission-editor-read/plugin.yaml
@@ -0,0 +1,15 @@
+id: permission-editor-read
+name: Editor Read Demo
+version: 1.0
+description: Adds a line word-count action that only reads editor text.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - editor.read
+
+contributes:
+ commands:
+ - line-word-count
+ menus:
+ - editor-line:Count Words On This Line:line-word-count
diff --git a/data/plugins/permission-editor-write/plugin.yaml b/data/plugins/permission-editor-write/plugin.yaml
new file mode 100644
index 0000000..3a55a33
--- /dev/null
+++ b/data/plugins/permission-editor-write/plugin.yaml
@@ -0,0 +1,15 @@
+id: permission-editor-write
+name: Editor Write Demo
+version: 1.0
+description: Adds an action that inserts a section banner above the clicked line.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - editor.write
+
+contributes:
+ commands:
+ - insert-section-banner
+ menus:
+ - editor-line:Insert Section Banner:insert-section-banner
diff --git a/data/plugins/permission-git/plugin.yaml b/data/plugins/permission-git/plugin.yaml
new file mode 100644
index 0000000..970aca1
--- /dev/null
+++ b/data/plugins/permission-git/plugin.yaml
@@ -0,0 +1,16 @@
+id: permission-git
+name: Git Blame Demo
+version: 1.0
+description: Adds Git blame to the right-click editor line menu.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - editor.read
+ - git
+
+contributes:
+ commands:
+ - git-blame-line
+ menus:
+ - editor-line:Git Blame This Line:git-blame-line
diff --git a/data/plugins/permission-native/native_demo.c b/data/plugins/permission-native/native_demo.c
new file mode 100644
index 0000000..e6ebe0d
--- /dev/null
+++ b/data/plugins/permission-native/native_demo.c
@@ -0,0 +1,44 @@
+/**
+ * @file native_demo.c
+ * @brief Example native Graptoς plugin.
+ * @details The manifest owns identity and permissions. This shared library
+ * owns the command label and behavior.
+ */
+
+#include "plugin_api.h"
+
+/**
+ * @brief Show text for the active editor line.
+ * @param context Command context supplied by Graptoς.
+ * @param user_data Plugin data supplied during registration.
+ */
+static void native_demo_line_info(GraptosPluginCommandContext *context,
+ gpointer user_data) {
+ (void)user_data;
+ guint line = graptos_plugin_context_line(context);
+ char *text = graptos_plugin_context_line_text(context, line);
+ char *body = g_strdup_printf("Line: %u\n\n%s", line, text ? text : "");
+ graptos_plugin_context_show_output(context,
+ "Native Demo",
+ "Native Line Info",
+ body);
+ g_free(body);
+ g_free(text);
+}
+
+/**
+ * @brief Register this plugin with Graptoς.
+ * @param host Host capability object supplied by Graptoς.
+ * @return TRUE when registration succeeds.
+ */
+gboolean graptos_plugin_register(GraptosPluginHost *host) {
+ if (graptos_plugin_host_api_version(host) != GRAPTOS_PLUGIN_API_VERSION) {
+ return FALSE;
+ }
+ return graptos_plugin_host_register_editor_line_command(host,
+ "native-demo-line-info",
+ "Native Demo Line Info",
+ native_demo_line_info,
+ NULL,
+ NULL);
+}
diff --git a/data/plugins/permission-native/plugin.yaml b/data/plugins/permission-native/plugin.yaml
new file mode 100644
index 0000000..4249bb6
--- /dev/null
+++ b/data/plugins/permission-native/plugin.yaml
@@ -0,0 +1,12 @@
+id: permission-native
+name: Native ABI Demo
+version: 1.0
+description: Demonstrates the native permission and ABI manifest shape.
+graptos_api_version: 1
+enabled: true
+native: lib/libgraptos_native_demo.so
+
+permissions:
+ - native
+ - editor.read
+ - ui
diff --git a/data/plugins/permission-project-read/plugin.yaml b/data/plugins/permission-project-read/plugin.yaml
new file mode 100644
index 0000000..9274ad8
--- /dev/null
+++ b/data/plugins/permission-project-read/plugin.yaml
@@ -0,0 +1,15 @@
+id: permission-project-read
+name: Project Read Demo
+version: 1.0
+description: Shows a small project summary without writing files.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - project.read
+
+contributes:
+ commands:
+ - project-summary
+ menus:
+ - editor-line:Show Project Summary:project-summary
diff --git a/data/plugins/permission-project-write/plugin.yaml b/data/plugins/permission-project-write/plugin.yaml
new file mode 100644
index 0000000..371eb54
--- /dev/null
+++ b/data/plugins/permission-project-write/plugin.yaml
@@ -0,0 +1,13 @@
+id: permission-project-write
+name: Project Write Demo
+version: 1.0
+description: Demonstrates the project.write permission shape for future scaffolding commands.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - project.write
+
+contributes:
+ commands:
+ - create-project-note
diff --git a/data/plugins/permission-syntax/plugin.yaml b/data/plugins/permission-syntax/plugin.yaml
new file mode 100644
index 0000000..8136fd7
--- /dev/null
+++ b/data/plugins/permission-syntax/plugin.yaml
@@ -0,0 +1,13 @@
+id: permission-syntax
+name: QML Syntax Demo
+version: 1.0
+description: Contributes a small QML syntax definition.
+graptos_api_version: 1
+enabled: false
+
+permissions:
+ - syntax
+
+contributes:
+ syntaxes:
+ - syntax
diff --git a/data/plugins/permission-syntax/syntax/qml.yaml b/data/plugins/permission-syntax/syntax/qml.yaml
new file mode 100644
index 0000000..46e018f
--- /dev/null
+++ b/data/plugins/permission-syntax/syntax/qml.yaml
@@ -0,0 +1,73 @@
+name: QML Demo
+extensions: [.qml]
+line_comment: "//"
+auto_indent: true
+indent_openers: ["{"]
+indent_closers: ["}"]
+close_pairs: ["(=>)", "{=>}", "[=>]", "\"=>\""]
+icon: qml
+filenames: []
+index: true
+import_style: none
+lsp_command:
+lsp_language_id: qml
+lsp_args: []
+import_keywords: [import]
+import_from_keywords: []
+import_member_keywords: []
+import_roots: [.]
+import_env: []
+import_extensions: [.qml, .js]
+import_member_files: []
+import_strip_extensions: true
+import_dot_modules: false
+import_static_modules: []
+completions: [import, Item, Rectangle, Text, Image, MouseArea, property, signal, function, anchors, parent, width, height, color]
+line_close_pairs: []
+statement_required_enders: []
+statement_exempt_prefixes: ["//", "import", "property", "signal"]
+statement_exempt_suffixes: ["{", "}", ";"]
+formatting:
+ enabled: false
+ scope: selection_or_block
+ profile: brace_semicolon
+ block_style: braces
+ statement_style: semicolon
+ brace_style: attach
+ one_statement_per_line: false
+ space_before_block_opener: true
+ space_after_comma: true
+ space_after_control_keyword: true
+ space_around_binary_operators: true
+ space_around_logical_operators: true
+ logical_operators: ["&&", "||"]
+ binary_operators: ["=", ":", "+", "-", "*", "/"]
+ unary_prefix_operators: ["!"]
+ protected_scopes: [comment, string]
+ pointer_alignment: name
+ continuation_indent: 4
+ max_column: 100
+ wrap_after_comma: false
+ case_indent: 0
+ preserve_blank_lines: true
+ max_blank_lines: 1
+ collapse_empty_blocks: false
+rules:
+ - name: comment
+ scope: comment
+ pattern: "//.*$"
+ color: "#6A9955"
+ - name: string
+ scope: string
+ pattern: "\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'"
+ color: "#CE9178"
+ - name: keyword
+ pattern: "(?
+#include
+
+/**
+ * @brief Runner command for one file type.
+ */
+typedef struct {
+ const char *extension; /**< File extension including the dot. */
+ const char *program; /**< Program used to run the file. */
+} RunFileRunner;
+
+/**
+ * @brief Known extension runners.
+ */
+static const RunFileRunner RUNNERS[] = {
+ { ".py", "python3" },
+ { ".js", "node" },
+ { ".mjs", "node" },
+ { ".sh", "bash" },
+ { ".bash", "bash" },
+ { ".rb", "ruby" },
+ { ".pl", "perl" },
+ { ".lua", "lua" },
+ { ".php", "php" },
+};
+
+/**
+ * @brief Return a runner for a path extension.
+ * @param path Active file path.
+ * @return Program name, or NULL when no extension runner is known.
+ */
+static const char *runner_for_path(const char *path) {
+ if (!path) return NULL;
+ for (guint i = 0u; i < G_N_ELEMENTS(RUNNERS); i++) {
+ if (g_str_has_suffix(path, RUNNERS[i].extension)) {
+ return RUNNERS[i].program;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * @brief Format process output for the result dialog.
+ * @param path File path that was run.
+ * @param program Program used to run it.
+ * @param status Process wait status.
+ * @param stdout_text Captured stdout.
+ * @param stderr_text Captured stderr.
+ * @return Newly allocated dialog body.
+ */
+static char *format_output(const char *path,
+ const char *program,
+ gint status,
+ const char *stdout_text,
+ const char *stderr_text) {
+ return g_strdup_printf("Command: %s %s\nExit status: %d\n\nstdout:\n%s\n\nstderr:\n%s",
+ program ? program : "",
+ path ? path : "",
+ status,
+ stdout_text && stdout_text[0] ? stdout_text : "(empty)",
+ stderr_text && stderr_text[0] ? stderr_text : "(empty)");
+}
+
+/**
+ * @brief Run the active file and show captured output.
+ * @param context Command context supplied by Graptoς.
+ * @param user_data Plugin data supplied during registration.
+ */
+static void run_active_file(GraptosPluginCommandContext *context,
+ gpointer user_data) {
+ (void)user_data;
+ char *path = graptos_plugin_context_file_path(context);
+ if (!path || !path[0]) {
+ graptos_plugin_context_show_output(context,
+ "Run Active File",
+ "No Saved File",
+ "Save the active tab before running it.");
+ g_free(path);
+ return;
+ }
+
+ const char *program = runner_for_path(path);
+ gboolean executable = g_file_test(path, G_FILE_TEST_IS_EXECUTABLE);
+ if (!program && !executable) {
+ char *body = g_strdup_printf("No runner is configured for this file:\n%s\n\nSupported extensions: .py, .js, .mjs, .sh, .bash, .rb, .pl, .lua, .php\nExecutable files are run directly.",
+ path);
+ graptos_plugin_context_show_output(context,
+ "Run Active File",
+ "Unsupported File Type",
+ body);
+ g_free(body);
+ g_free(path);
+ return;
+ }
+
+ char *argv_with_runner[] = { (char *)program, path, NULL };
+ char *argv_executable[] = { path, NULL };
+ char **argv = program ? argv_with_runner : argv_executable;
+ char *cwd = g_path_get_dirname(path);
+ char *stdout_text = NULL;
+ char *stderr_text = NULL;
+ GError *error = NULL;
+ gint status = 0;
+ gboolean ok = g_spawn_sync(cwd,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL,
+ NULL,
+ &stdout_text,
+ &stderr_text,
+ &status,
+ &error);
+ if (!ok) {
+ char *body = g_strdup_printf("Failed to run:\n%s\n\n%s",
+ path,
+ error ? error->message : "Unknown error");
+ graptos_plugin_context_show_output(context,
+ "Run Active File",
+ "Run Failed",
+ body);
+ g_free(body);
+ g_clear_error(&error);
+ } else {
+ char *body = format_output(path,
+ program ? program : path,
+ status,
+ stdout_text,
+ stderr_text);
+ graptos_plugin_context_show_output(context,
+ "Run Active File",
+ "Run Output",
+ body);
+ g_free(body);
+ }
+
+ g_free(stdout_text);
+ g_free(stderr_text);
+ g_free(cwd);
+ g_free(path);
+}
+
+/**
+ * @brief Register this plugin with Graptoς.
+ * @param host Host capability object supplied by Graptoς.
+ * @return TRUE when registration succeeds.
+ */
+gboolean graptos_plugin_register(GraptosPluginHost *host) {
+ if (graptos_plugin_host_api_version(host) != GRAPTOS_PLUGIN_API_VERSION) {
+ return FALSE;
+ }
+ return graptos_plugin_host_register_editor_line_command(host,
+ "run-active-file",
+ "Run Active File",
+ run_active_file,
+ NULL,
+ NULL);
+}
diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md
new file mode 100644
index 0000000..b6cd7ac
--- /dev/null
+++ b/docs/PLUGINS.md
@@ -0,0 +1,426 @@
+# Plugins
+
+Graptoς plugins are local folders with a `plugin.yaml` manifest. The plugin
+layer is the boundary between Graptoς internals and extensions: plugins declare
+what they contribute, and Graptoς decides how those contributions are loaded and
+executed.
+
+Version 1 is intentionally small. It supports declarative plugin discovery,
+syntax and template directories, command declarations, editor context-menu
+items, and a bottom-bar Plugins tool panel. Native plugin loading has an ABI
+stub, but Graptoς does not auto-load native code at startup.
+
+## Plugin locations
+
+Graptoς scans these plugin roots:
+
+```text
+~/.local/share/graptos/plugins//
+${prefix}/share/graptos/plugins//
+data/plugins//
+```
+
+Use `data/plugins/` while developing inside the source tree. Installed builds
+use `${prefix}/share/graptos/plugins/`.
+
+To disable plugin discovery for tests or debugging:
+
+```sh
+GRAPTOS_DISABLE_PLUGINS=1 graptos
+```
+
+## Basic layout
+
+```text
+data/plugins/my-plugin/
+ plugin.yaml
+ syntax/
+ templates/
+```
+
+Only `plugin.yaml` is required. Other directories are needed only when the
+manifest points at them.
+
+## Manifest
+
+Minimal manifest:
+
+```yaml
+id: my-plugin
+name: My Plugin
+version: 1.0
+description: Short description.
+graptos_api_version: 1
+enabled: true
+
+permissions:
+ - editor.read
+
+contributes:
+ commands: []
+ menus: []
+```
+
+Required fields:
+
+- `id`: stable lowercase id. Use lowercase letters, numbers, `.`, `_`, or `-`.
+- `name`: visible plugin name.
+- `version`: plugin version string.
+- `graptos_api_version`: must match the current plugin API version, currently `1`.
+
+Optional fields:
+
+- `description`: short visible description.
+- `enabled`: `true` or `false`; defaults to `true`.
+- `native`: relative or absolute path to a shared library. Parsed, but not
+ auto-loaded during normal startup.
+- `permissions`: declared capabilities.
+- `contributes`: plugin contribution lists.
+
+## Permissions
+
+Current known permission names:
+
+```text
+editor.read
+editor.write
+project.read
+project.write
+ui
+syntax
+theme
+template
+git
+terminal
+command
+native
+```
+
+Graptoς currently enforces permission checks for built-in plugin command
+execution where applicable. For example, the Git blame example requires `git`.
+Full permission prompt UI is not implemented yet.
+
+## Contributions
+
+### Syntax directories
+
+```yaml
+permissions:
+ - syntax
+
+contributes:
+ syntaxes:
+ - syntax
+```
+
+Each listed path is resolved relative to the plugin folder unless it is
+absolute. The directory should contain Graptoς syntax YAML files.
+
+### Project template directories
+
+```yaml
+permissions:
+ - template
+
+contributes:
+ templates:
+ - templates
+```
+
+Each listed directory should contain project-template subdirectories. Each
+template subdirectory has the same `template.yaml` format used by built-in
+project templates.
+
+### Editor commands
+
+Plugins can add editor-line actions. The same action appears in two places:
+
+- The editor right-click menu, where it uses the line under the pointer.
+- The bottom-bar Plugins tool panel, where it uses the active cursor line.
+
+```yaml
+permissions:
+ - editor.read
+ - git
+
+contributes:
+ commands:
+ - git-blame-line
+ menus:
+ - editor-line:Git Blame This Line:git-blame-line
+```
+
+Menu format:
+
+```text
+editor-line: