diff --git a/.editorconfig b/.editorconfig index 077fdea27..2ec353fc5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,3 +22,16 @@ quote_type = single [{Makefile,go.mod,go.sum,*.go,.gitmodules}] indent_size = 4 indent_style = tab + +[*.kt] +indent_size = 4 +indent_style = space +max_line_length = 100 + +[*.lua] +indent_size = 2 +indent_style = space + +[*.el] +indent_size = 2 +indent_style = space diff --git a/.github/workflows/ide_plugins.yml b/.github/workflows/ide_plugins.yml index e56ad657d..77ae9fec0 100644 --- a/.github/workflows/ide_plugins.yml +++ b/.github/workflows/ide_plugins.yml @@ -46,19 +46,24 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 + with: + gradle-version: 8.5 + + - name: Check Kotlin formatting + run: ./scripts/format_kotlin_files --check - name: Build Plugin working-directory: packages/jetbrains - run: ./gradlew buildPlugin + run: gradle buildPlugin - name: Verify Plugin working-directory: packages/jetbrains - run: ./gradlew verifyPlugin + run: gradle verifyPlugin - name: Publish Plugin if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.modified, 'packages/jetbrains/src/main/resources/META-INF/plugin.xml') working-directory: packages/jetbrains - run: ./gradlew publishPlugin + run: gradle publishPlugin env: PUBLISH_TOKEN: ${{ secrets.JETBRAINS_TOKEN }} @@ -69,12 +74,8 @@ jobs: # Simple check for syntax validity - uses: actions/checkout@v4 - - name: Check Lua syntax - uses: JohnnyMorganz/stylua-action@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - version: latest - args: --check packages/vim/lua/ + - name: Check Lua formatting + run: ./scripts/format_lua_files --check emacs: name: Emacs Mode diff --git a/examples/features.prompt b/examples/features.prompt index 015dd378d..64ef4a595 100644 --- a/examples/features.prompt +++ b/examples/features.prompt @@ -1,3 +1,19 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + --- model: googleai/gemini-pro input: diff --git a/packages/emacs/dotprompt-mode.el b/packages/emacs/dotprompt-mode.el index be9118ded..1228e9e80 100644 --- a/packages/emacs/dotprompt-mode.el +++ b/packages/emacs/dotprompt-mode.el @@ -57,26 +57,75 @@ (defvar dotprompt-font-lock-keywords (list - ;; License header comments (lines starting with #) - '("^#.*$" . font-lock-comment-face) + ;; YAML frontmatter delimiters + '("^---$" . font-lock-preprocessor-face) + + ;; YAML keys in frontmatter (word followed by colon at start of line or after indentation) + '("^\\s-*\\([a-zA-Z_][a-zA-Z0-9_-]*\\):" 1 font-lock-keyword-face) + + ;; YAML string values (unquoted, after colon and space) + '(":\\s-+\\([a-zA-Z][a-zA-Z0-9._/-]*\\)$" 1 font-lock-string-face) + + ;; YAML numeric values + '(":\\s-+\\([0-9]+\\.?[0-9]*\\)$" 1 font-lock-constant-face) - ;; Markers <<>> + ;; Dotprompt markers <<>> etc. '("<<]+>>>" . font-lock-preprocessor-face) - ;; Partials {{> partialName}} - '("{{>\\s-*[a-zA-Z0-9_.-]+\\s-*\\(.*?\\)}}" . font-lock-builtin-face) + ;; Handlebars block comments {{!-- ... --}} + '("{{!--\\(.\\|\n\\)*?--}}" . font-lock-comment-face) + + ;; Handlebars inline comments {{! ... }} + '("{{![^}]*}}" . font-lock-comment-face) + + ;; Partials {{> partialName}} - highlight partial indicator and name + '("{{\\(>\\)\\s-*\\([a-zA-Z0-9_.-]+\\)" + (1 font-lock-keyword-face) + (2 font-lock-builtin-face)) + + ;; Block opening helpers {{#each items}} - highlight # + keyword AND the argument + '("{{\\(#\\)\\(if\\|unless\\|each\\|with\\|log\\|lookup\\|ifEquals\\|unlessEquals\\)\\(?:\\s-+\\([a-zA-Z0-9_.-]+\\)\\)?" + (1 font-lock-keyword-face) + (2 font-lock-keyword-face) + (3 font-lock-variable-name-face nil t)) + + ;; Block closing helpers {{/each}} - highlight / and keyword + '("{{\\(/\\)\\(if\\|unless\\|each\\|with\\|log\\|lookup\\|ifEquals\\|unlessEquals\\)}}" + (1 font-lock-keyword-face) + (2 font-lock-keyword-face)) - ;; Handlebars Control Flow {{#if}} {{/if}} - '("{{[#/]\\(if\\|unless\\|each\\|with\\|log\\|lookup\\|else\\)" 1 font-lock-keyword-face) + ;; Else keyword {{else}} + '("{{\\(else\\)}}" 1 font-lock-keyword-face) - ;; Dotprompt Helpers (distinct color) - '("\\<\\(json\\|role\\|history\\|section\\|media\\|ifEquals\\|unlessEquals\\)\\>" . font-lock-function-name-face) + ;; Dotprompt helpers {{json this indent=2}} + '("{{\\(json\\|role\\|history\\|section\\|media\\|ifEquals\\|unlessEquals\\)" + 1 font-lock-function-name-face) - ;; General tags - '("{{" . font-lock-delimiter-face) - '("}}" . font-lock-delimiter-face) + ;; Helper named parameters (param=value) + '("\\b\\([a-zA-Z_][a-zA-Z0-9_]*\\)=" 1 font-lock-type-face) + + ;; Quoted strings inside handlebars + '("{{[^}]*\\([\"'][^\"']*[\"']\\)" 1 font-lock-string-face) + + ;; Boolean and null constants inside handlebars + '("{{[^}]*\\b\\(true\\|false\\|null\\|undefined\\)\\b" 1 font-lock-constant-face) + + ;; Numbers inside handlebars + '("{{[^}]*\\b\\([0-9]+\\)\\b" 1 font-lock-constant-face) + + ;; Simple variable references {{variableName}} or {{this}} + '("{{\\([a-zA-Z_][a-zA-Z0-9_.]*\\)}}" 1 font-lock-variable-name-face) + + ;; The handlebars delimiters themselves + '("\\({{\\)" 1 font-lock-constant-face) + '("\\(}}\\)" 1 font-lock-constant-face) + + ;; License header comments (lines starting with #) + ;; This is at the END with override flag `t` to ensure entire comment lines + ;; are highlighted uniformly, overriding any patterns that matched within them + '("^#.*$" 0 font-lock-comment-face t) ) - "Minimal highlighting for Dotprompt.") + "Syntax highlighting for Dotprompt templates.") (defvar dotprompt-mode-map (let ((map (make-sparse-keymap))) @@ -132,7 +181,7 @@ Otherwise, call promptly fmt directly." (progn (write-region (point-min) (point-max) temp-file nil 'silent) (let ((exit-code (call-process dotprompt-promptly-path nil nil nil - "fmt" temp-file))) + "fmt" temp-file))) (when (zerop exit-code) (erase-buffer) (insert-file-contents temp-file) diff --git a/packages/jetbrains/README.md b/packages/jetbrains/README.md index 9eeaf4d81..92264d8d6 100644 --- a/packages/jetbrains/README.md +++ b/packages/jetbrains/README.md @@ -32,7 +32,7 @@ Language support for Dotprompt (`.prompt`) files in JetBrains IDEs (IntelliJ IDE 2. **Build the plugin**: ```bash cd packages/jetbrains - ./gradlew buildPlugin + gradle buildPlugin ``` 3. **Install**: @@ -65,13 +65,13 @@ cargo build --release -p promptly ```bash # Build the plugin -./gradlew buildPlugin +gradle buildPlugin # Run IDE with plugin for testing -./gradlew runIde +gradle runIde # Run tests -./gradlew test +gradle test ``` ## Live Templates diff --git a/packages/jetbrains/build.gradle.kts b/packages/jetbrains/build.gradle.kts index 5449d70b0..b5f308df2 100644 --- a/packages/jetbrains/build.gradle.kts +++ b/packages/jetbrains/build.gradle.kts @@ -17,47 +17,44 @@ */ plugins { - id("java") - id("org.jetbrains.kotlin.jvm") version "1.9.22" - id("org.jetbrains.intellij") version "1.17.2" + id("java") + id("org.jetbrains.kotlin.jvm") version "1.9.22" + id("org.jetbrains.intellij") version "1.17.2" } group = "com.google.dotprompt" + version = "0.1.0" -repositories { - mavenCentral() -} +repositories { mavenCentral() } intellij { - version.set("2024.1") - type.set("IC") // IntelliJ IDEA Community Edition - plugins.set(listOf( - "com.redhat.devtools.lsp4ij:0.0.2" // LSP4IJ for LSP support - )) + version.set("2024.1") + type.set("IC") // IntelliJ IDEA Community Edition + plugins.set( + listOf( + "com.redhat.devtools.lsp4ij:0.0.2" // LSP4IJ for LSP support + ) + ) } tasks { - withType { - sourceCompatibility = "17" - targetCompatibility = "17" - } - withType { - kotlinOptions.jvmTarget = "17" - } - - patchPluginXml { - sinceBuild.set("241") - untilBuild.set("243.*") - } - - signPlugin { - certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) - privateKey.set(System.getenv("PRIVATE_KEY")) - password.set(System.getenv("PRIVATE_KEY_PASSWORD")) - } - - publishPlugin { - token.set(System.getenv("PUBLISH_TOKEN")) - } + withType { + sourceCompatibility = "17" + targetCompatibility = "17" + } + withType { kotlinOptions.jvmTarget = "17" } + + patchPluginXml { + sinceBuild.set("241") + untilBuild.set("243.*") + } + + signPlugin { + certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) + privateKey.set(System.getenv("PRIVATE_KEY")) + password.set(System.getenv("PRIVATE_KEY_PASSWORD")) + } + + publishPlugin { token.set(System.getenv("PUBLISH_TOKEN")) } } diff --git a/packages/jetbrains/src/main/kotlin/com/google/dotprompt/PromptlyServerFactory.kt b/packages/jetbrains/src/main/kotlin/com/google/dotprompt/PromptlyServerFactory.kt index a1ed00227..872733ee0 100644 --- a/packages/jetbrains/src/main/kotlin/com/google/dotprompt/PromptlyServerFactory.kt +++ b/packages/jetbrains/src/main/kotlin/com/google/dotprompt/PromptlyServerFactory.kt @@ -30,7 +30,7 @@ class PromptlyServerFactory : LanguageServerFactory { override fun createConnectionProvider(project: Project): StreamConnectionProvider { val promptlyPath = findPromptlyExecutable() val commands = listOf(promptlyPath, "lsp") - return ProcessStreamConnectionProvider(commands) + return object : ProcessStreamConnectionProvider(commands, project.basePath) {} } /** diff --git a/packages/vim/lua/dotprompt/init.lua b/packages/vim/lua/dotprompt/init.lua index 80fc2236c..22f16b495 100644 --- a/packages/vim/lua/dotprompt/init.lua +++ b/packages/vim/lua/dotprompt/init.lua @@ -131,7 +131,10 @@ function M.setup(opts) capabilities = vim.lsp.protocol.make_client_capabilities(), }) - vim.notify("Dotprompt: LSP configured with " .. promptly_path, vim.log.levels.INFO) + vim.notify( + "Dotprompt: LSP configured with " .. promptly_path, + vim.log.levels.INFO + ) end --- Manually trigger document formatting diff --git a/scripts/fmt b/scripts/fmt index 390547a92..31f2d9c63 100755 --- a/scripts/fmt +++ b/scripts/fmt @@ -38,6 +38,9 @@ if command -v rust-parallel >/dev/null 2>&1; then "${TOP_DIR}/scripts/format_handlebarrz_files" \ "${TOP_DIR}/scripts/format_toml_files" \ "${TOP_DIR}/scripts/format_ts_files" \ + "${TOP_DIR}/scripts/format_kotlin_files" \ + "${TOP_DIR}/scripts/format_elisp_files" \ + "${TOP_DIR}/scripts/format_lua_files" \ | rust-parallel -s --exit-on-error || exit 1 else echo "rust-parallel was not detected; not performing formatting in parallel" @@ -48,6 +51,9 @@ else "${TOP_DIR}/scripts/format_rust_files" || exit 1 "${TOP_DIR}/scripts/format_toml_files" || exit 1 "${TOP_DIR}/scripts/format_ts_files" || exit 1 + "${TOP_DIR}/scripts/format_kotlin_files" || exit 1 + "${TOP_DIR}/scripts/format_elisp_files" || exit 1 + "${TOP_DIR}/scripts/format_lua_files" || exit 1 fi # Do this after all of the above have completed. diff --git a/scripts/format_elisp_files b/scripts/format_elisp_files new file mode 100755 index 000000000..cd59ceb37 --- /dev/null +++ b/scripts/format_elisp_files @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Script to format all Elisp files in the workspace using Emacs. + +set -euo pipefail + +if ! command -v emacs &> /dev/null; then + echo "Emacs not found, skipping Elisp formatting." + exit 0 +fi + +# Find all .el files +ELISP_FILES=$(find . -type d \( -name .git -o -name .cache -o -name node_modules \) -prune -o -name "*.el" -print) + +if [ -z "${ELISP_FILES}" ]; then + echo "No Elisp files found to format." + exit 0 +fi + +echo "=== Formatting Elisp files... ===" +for file in ${ELISP_FILES}; do + echo "Formatting ${file}..." + emacs -Q -batch "${file}" \ + --eval "(defun indent-buffer () (interactive) (save-excursion (indent-region (point-min) (point-max) nil)))" \ + --eval "(indent-buffer)" \ + -f save-buffer +done +echo "=== Elisp formatting complete. ===" diff --git a/scripts/format_kotlin_files b/scripts/format_kotlin_files new file mode 100755 index 000000000..ce56ec06f --- /dev/null +++ b/scripts/format_kotlin_files @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Script to format all Kotlin files in the workspace using ktfmt. + +set -euo pipefail + +# Parse arguments +CHECK_MODE=false +if [[ $# -gt 0 && ($1 == "--check" || $1 == "-c") ]]; then + CHECK_MODE=true +fi + +VERSION="0.61" +JAR_NAME="ktfmt-${VERSION}-with-dependencies.jar" +JAR_URL="https://repo1.maven.org/maven2/com/facebook/ktfmt/${VERSION}/${JAR_NAME}" +CACHE_DIR=".cache" +JAR_PATH="${CACHE_DIR}/${JAR_NAME}" + +# Create cache directory if it doesn't exist +mkdir -p "${CACHE_DIR}" + +# Download the JAR if it doesn't exist +if [ ! -f "${JAR_PATH}" ]; then + echo "Downloading ${JAR_NAME} to ${CACHE_DIR}..." + if curl -fL "${JAR_URL}" -o "${JAR_PATH}"; then + echo "Download successful." + else + echo "Error: Failed to download ${JAR_NAME}." + rm -f "${JAR_PATH}" + exit 1 + fi +fi + +# Find all Kotlin files, excluding build and .cache directories +KOTLIN_FILES=$(find . -type d \( -name .git -o -name .cache -o -name build -o -name out \) -prune -o -name "*.kt" -o -name "*.kts" -print) + +if [ -z "${KOTLIN_FILES}" ]; then + echo "No Kotlin files found to format." + exit 0 +fi + +if [ "$CHECK_MODE" = true ]; then + echo "=== Checking Kotlin formatting... ===" + java -jar "${JAR_PATH}" --google-style --set-exit-if-changed --dry-run ${KOTLIN_FILES} + echo "✅ Kotlin formatting check passed." +else + echo "=== Formatting Kotlin files... ===" + java -jar "${JAR_PATH}" --google-style ${KOTLIN_FILES} + echo "=== Kotlin formatting complete. ===" +fi diff --git a/scripts/format_lua_files b/scripts/format_lua_files new file mode 100755 index 000000000..4a75755f9 --- /dev/null +++ b/scripts/format_lua_files @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Script to format all Lua files in the workspace using StyLua. + +set -euo pipefail + +# Parse arguments +ARGS="" +if [[ $# -gt 0 && ($1 == "--check" || $1 == "-c") ]]; then + ARGS="--check" +fi + +# Try to find stylua +if command -v stylua &> /dev/null; then + STYLUA="stylua" +else + # Check if in node_modules + if [ -f "./node_modules/.bin/stylua" ]; then + STYLUA="./node_modules/.bin/stylua" + else + echo "StyLua not found, skipping Lua formatting. Install with: cargo install stylua" + exit 0 + fi +fi + +# Find all .lua files +LUA_FILES=$(find . -type d \( -name .git -o -name .cache -o -name node_modules \) -prune -o -name "*.lua" -print) + +if [ -z "${LUA_FILES}" ]; then + echo "No Lua files found to format." + exit 0 +fi + +if [[ "$ARGS" == "--check" ]]; then + echo "=== Checking Lua formatting... ===" +else + echo "=== Formatting Lua files... ===" +fi + +$STYLUA $ARGS ${LUA_FILES} + +if [[ "$ARGS" == "--check" ]]; then + echo "✅ Lua formatting check passed." +else + echo "=== Lua formatting complete. ===" +fi diff --git a/scripts/install_emacs_mode b/scripts/install_emacs_mode index 6284164a3..d0baffdb9 100755 --- a/scripts/install_emacs_mode +++ b/scripts/install_emacs_mode @@ -27,7 +27,6 @@ usage() { echo "Usage: $0 [options]" echo "Options:" echo " -d, --dest DIR Destination directory (default: ~/.emacs.d/lisp)" - echo " -f, --force Overwrite existing files without prompting" echo " --no-lsp Skip building promptly LSP server" echo " -h, --help Show this help message" exit 1 @@ -35,13 +34,11 @@ usage() { # Parse arguments DEST_DIR="" -FORCE=false BUILD_LSP=true while [[ "$#" -gt 0 ]]; do case $1 in -d|--dest) DEST_DIR="$2"; shift ;; - -f|--force) FORCE=true ;; --no-lsp) BUILD_LSP=false ;; -h|--help) usage ;; *) echo "Unknown parameter passed: $1"; usage ;; @@ -49,9 +46,21 @@ while [[ "$#" -gt 0 ]]; do shift done +# Detect Doom Emacs configuration directory +DOOM_CONFIG_DIR="" +if [[ -d "$HOME/.doom.d" ]]; then + DOOM_CONFIG_DIR="$HOME/.doom.d" +elif [[ -d "$HOME/.config/doom" ]]; then + DOOM_CONFIG_DIR="$HOME/.config/doom" +fi + # Determine destination if not specified if [[ -z "$DEST_DIR" ]]; then - DEST_DIR="$EMACS_LISP_DIR" + if [[ -n "$DOOM_CONFIG_DIR" ]]; then + DEST_DIR="$DOOM_CONFIG_DIR/lisp" + else + DEST_DIR="$EMACS_LISP_DIR" + fi fi echo "==========================================" @@ -96,18 +105,37 @@ echo "Step 2: Installing Emacs mode to: $DEST_DIR" # Create directory mkdir -p "$DEST_DIR" -# Copy file +# Copy file (always overwrite - this is an installer/updater) echo " Copying dotprompt-mode.el..." -if [ -f "$DEST_DIR/dotprompt-mode.el" ] && [ "$FORCE" = false ]; then - read -p " File '$DEST_DIR/dotprompt-mode.el' exists. Overwrite? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo " Skipping file copy." +cp "$EMACS_PKG_DIR/dotprompt-mode.el" "$DEST_DIR/" +echo " ✓ Installed dotprompt-mode.el" + +# Doom Emacs Auto-config +CONFIG_UPDATED=false +ALREADY_CONFIGURED=false +if [[ -n "$DOOM_CONFIG_DIR" ]]; then + echo "" + echo "Step 3: Doom Emacs detected in $DOOM_CONFIG_DIR" + CONFIG_EL="$DOOM_CONFIG_DIR/config.el" + if [[ -f "$CONFIG_EL" ]]; then + if grep -q "dotprompt-mode" "$CONFIG_EL"; then + echo " ✓ dotprompt-mode already configured in $CONFIG_EL" + ALREADY_CONFIGURED=true + else + echo " Updating $CONFIG_EL..." + cat >> "$CONFIG_EL" < /dev/null; then + gradle buildPlugin else - # Download gradle wrapper if not present - gradle wrapper - ./gradlew buildPlugin + echo "Error: gradle not found. Please install Gradle: https://gradle.org/install/" + exit 1 fi # Find the built plugin @@ -126,8 +125,7 @@ echo " 2. Go to Settings/Preferences → Plugins → ⚙️ → Install Plugin echo " 3. Select: $JETBRAINS_DIR/$PLUGIN_ZIP" echo "" -# Optionally run the IDE for testing if [ "$RUN_IDE" = true ]; then echo "Launching IDE with plugin for testing..." - ./gradlew runIde + gradle runIde fi