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 @@ -![cleaf](https://i.snipboard.io/1oBVMY.jpg) +
+ +drawing + +### γραπτός: Grap-Tos; adj, written or inscribed -# Graptoς _Previously Cleaf_ +[![C CI](https://github.com/Logic-gate/Graptos/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/Logic-gate/Graptos/actions/workflows/c-cpp.yml) +![C](https://img.shields.io/badge/language-C-blue) +![Open Source Friendly](https://img.shields.io/badge/open--source-friendly-22c55e) +![Pull Requests Welcome](https://img.shields.io/badge/pull%20requests-welcome-2563eb) + +
+ +![cleaf](https://i.snipboard.io/1oBVMY.jpg) -γραπτός: 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: