Skip to content
setdefPublic

About

Unofficial, community support for the Inko programming language in JetBrains IDEs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Parakeet for Inko

Certified Shovelware

Parakeet is unofficial, community-made support for the Inko programming language in JetBrains IDEs. It's tested in IntelliJ IDEA, and should work in the others (RubyMine, WebStorm, RustRover, etc.), though they haven't been tried yet.

This plugin is not affiliated with or endorsed by the Inko project. Please don't report problems with this plugin to the Inko project; open an issue here instead. When Parakeet causes an error, the IDE's error dialog can do that for you: Open a GitHub Issue opens one in your browser, filled in with the error and your versions, for you to review and submit.

How this code came to exist

I use three words for how code comes to exist. Code I wrote, I typed by hand, with nothing generated. Code I wrought, an AI agent generated while I iterated with it, and I reviewed every change before committing it, so I own every diff. Code I requested, an agent built with little or no review from me, the way a dark factory runs without lights.

Versions 0.1.0 through 0.15.0 are requested.

Features

  • An Inko file type for .inko files, with its own icon
  • Syntax highlighting, from the TextMate grammar in inko-lang/vscode
  • Line comment toggling (#), bracket matching, and auto-closing brackets and quotes
  • Two-space indentation as you type, like inko fmt (Settings → Editor → Code Style → Inko)
  • Compiler errors and warnings as you type, from Cuttlebone (the same as inko check), with quick fixes (Alt+Enter): import an undefined name from the module that defines it, remove an unused import, rename an unused local to _name, discard an unused result with let _ =, add a missing field, implement missing methods or a missing required trait, and remove unreachable code
  • Errors across the whole project, as you type: Cuttlebone checks every module, so files with errors are marked in the Project view and the Problems tool window even when they aren't open, and Code → Inspect Code lists every module's errors and warnings
  • Intentions (Alt+Enter anywhere): flip a comparison, ==, !=, and, or or; invert an if; replace an if/else if chain over literals with a match, and back; add or remove argument names; and specify or remove a let's type
  • Style-guide inspections for what inko fmt doesn't fix, with quick fixes: names in the wrong case, Bool methods not ending in ? (or prefixed with is_), redundant argument names like start: start, and empty parentheses in calls like pop(). They're weak warnings, configurable in Settings → Editor → Inspections → Inko, and # noinspection Name on the line above suppresses one.
  • Code → Reformat Code (⌥⌘L / Ctrl+Alt+L) as inko fmt formats, from Cuttlebone (which runs Inko's own formatter on the unsaved text). To format on save, turn on Settings → Tools → Actions on Save → Reformat code.
  • Go to definition (⌘-click / Ctrl-click, ⌘B / Ctrl+B) for types, traits, constants, functions, methods, fields, modules, and local variables and parameters, including the standard library and dependencies. A method call goes to the method the compiler calls, from Cuttlebone.
  • Go to Type Declaration (⇧⌘B / Ctrl+Shift+B)
  • Find Usages (⌥F7 / Alt+F7) and Rename (⇧F6 / Shift+F6) across the project, by the compiler's answers, including renaming a module's file, and a trait's method with the methods implementing it
  • Highlight usages of the name at the caret within the file (⌘⇧F7 / Ctrl+Shift+F7), with assignments (=, :=, += and the rest) as writes
  • Parameter info (⌘P / Ctrl+P) in calls, and parameter-name hints before positional arguments
  • Type hints for let bindings and closure arguments that don't write a type. Types and parameter names each have a switch in Settings → Editor → Inlay Hints
  • Trait gutter icons linking traits, their implementations, and the methods that implement them, with Go to Implementation (⌥⌘B / Ctrl+Alt+B) and Go to Super Method (⌘U / Ctrl+U)
  • Implement Methods (⌃I / Ctrl+I) and Override Methods (⌃O / Ctrl+O) in impl Trait for Type, stubbing the trait's methods
  • Generate (⌘N / Alt+Insert) in a type: a Constructor (fn static new from every field) and Implement Trait (an impl for a chosen trait, with its required methods stubbed and the trait imported)
  • Expression Type (⌃⇧P / Ctrl+Shift+P): the compiler's type
  • Go to Test (⌘⇧T / Ctrl+Shift+T) between src/app/util.inko and test/app/test_util.inko, and Create Test
  • Quick documentation (F1, or hover): a definition's signature, where it's defined, and its doc comment, and the type at the caret ("Type here"); ⇧F1 opens standard library definitions on docs.inko-lang.org
  • Go to Class / Go to Symbol (⌘O / ⌥⌘O) and Search Everywhere for Inko types, traits, methods, fields, and constants, from Cuttlebone's workspace symbols: the project's, its dependencies' and the standard library's, including what isn't saved yet
  • Structure view, breadcrumbs, and sticky lines for each file's declarations, from Cuttlebone's document symbols, through IntelliJ's LSP support
  • Code folding for blocks, argument lists, comment runs, and imports, from Cuttlebone (also in code that doesn't parse)
  • Optimize Imports (⌃⌥O / Ctrl+Alt+O): removes unused imports and sorts the rest
  • Refactorings: Change Signature (⌘F6 / Ctrl+F6), Extract Method (⌥⌘M / Ctrl+Alt+M), Inline Method and Inline Variable (⌥⌘N / Ctrl+Alt+N), Introduce Variable (⌥⌘V / Ctrl+Alt+V), Introduce Parameter (⌥⌘P / Ctrl+Alt+P), Introduce Constant (⌥⌘C / Ctrl+Alt+C), Safe Delete (⌘⌫ / Alt+Delete), Move (F6) for a top-level type, trait, constant, or function to another module, and moving files or directories and renaming directories (which updates their modules' imports)
  • Type Hierarchy (⌃H / Ctrl+H) for traits and the types implementing them, and Call Hierarchy (⌃⌥H / Ctrl+Alt+H) for callers and callees
  • Usage counts ("N usages") above declarations
  • Surround With (⌥⌘T / Ctrl+Alt+T) and Unwrap/Remove (⌘⇧⌫ / Ctrl+Shift+Delete), Move Statement (⇧⌘↑/↓ / Ctrl+Shift+↑/↓), Complete Current Statement (⇧⌘⏎ / Ctrl+Shift+Enter), and Extend Selection (⌥↑ / Ctrl+W) through calls, arguments, strings, brackets, statements, and declarations, from Cuttlebone (also in code that doesn't parse)
  • Copy Reference (⌥⇧⌘C / Ctrl+Alt+Shift+C), like app.util.Helper.new
  • New Inko File (File → New → Inko File): a module, a test, or a program
  • Inko highlighting in Markdown ```inko code fences
  • External Libraries: the standard library and each project's dependencies, browsable and searchable, with dep and build excluded from the project
  • inko.pkg: highlighting and errors, plus Tools → Inko Dependencies to sync, update, add, and remove dependencies, and a Sync button when inko.pkg changes
  • Completion is Cuttlebone's: members after . (the compiler's list), the names in scope (locals, the type's methods, the module's own and imported names, the prelude's), the public names of modules not imported yet, adding the import when you choose one, keywords where the grammar takes them, fields after @, and modules and symbols in an import. Where Cuttlebone can't answer for the code being edited, Parakeet offers the locals and parameters in scope, keywords, the file's fields, and after . members by name
  • Imports on paste: pasting Inko code into another module adds the imports it needs (Settings → Editor → General → Auto Import → Insert imports on paste)
  • Live templates: fn, fnp, type, typep, trait, impl, match, main, tests, and test, and postfix completion: .if, .match, .let, .not, .return, and .par after an expression
  • TODO highlighting and spell checking in comments
  • Test runner: results per test file and test, failures with expected and actual values, jumping to (or rerunning) a single test, and rerunning the failed tests
  • Run and test from the gutter: ▶ next to fn async main, next to fn pub tests in test files, and next to each t.test('…')
  • Run configurations for inko run, inko test, inko build, and inko check, with release builds, other targets, and static linking, and clickable file.inko:line links in the output
  • New Project wizard (File → New → Project → Inko) using inko init, optionally as a library or with a GitHub Actions workflow. The main module gets a valid module name (e.g. from_intellij for a project named "From IntelliJ"), which you can change.
  • Per-project toolchain in Settings → Languages & Frameworks → Inko: auto-detect, ivm's default, any version ivm has installed, or a custom executable. With ivm installed, you can also install and remove Inko versions there, and banners offer to install Inko when it's missing, or a newer one when inko.pkg requires it.

How it works

The plugin understands Inko through the inko command rather than its own compiler, so it always agrees with your installed Inko version.

  • Errors are Cuttlebone's pulled diagnostics (textDocument/diagnostic for the editor and Inspect Code, workspace/diagnostic for the whole project): the same as inko check reports, from a check of the whole project with your unsaved text, so imports in both directions and dependencies resolve.
  • Go to definition, quick documentation, parameter info and hints, and Implement Methods use the definitions Parakeet reads from the code: the project's src/ and test/, its dependencies in dep/, and the standard library (inko print std), with unsaved text in place of a file's, each file read again when it changes. Nothing is compiled for it, so it follows unsaved edits and code that doesn't compile.
  • Cuttlebone answers with the compiler's own types and definitions; see Cuttlebone for which features ask it. Parakeet requires it: without it, what needs the compiler's types (go to definition of anything but a local or a module, completion, parameter info, hints, usage highlighting, Expression Type, Find Usages, Rename and the refactorings built on it) is unavailable, and Parakeet says so once when the project opens. Each question has a time budget (0.4 s for resolving a name, 1.5 s for completion, parameter info, documentation and Expression Type, 2 s for usage highlighting and for a file's inlay hints, asked in one request, and 10 s for a search's references), and answers are kept until the code changes. Highlighting only waits for resolving names (inspections and intentions resolve them), the inlay hints and usage highlighting: Cuttlebone answers questions about unchanged text from the check it kept, in under a millisecond after a file's first check, which costs a whole check (about 50 ms for a small project, more on a busy machine). A question whose wait is cancelled because the caret moved isn't cancelled itself: that would discard the checks Cuttlebone kept, and its answer is kept for next time. An edit cancels ($/cancelRequest) every question about the text before it, since Cuttlebone (from its seventh milestone) would otherwise answer each one for that text, at a check each, for nobody: except what's still waiting for its answer, a search or completion (whose items are filtered as you type on). Usage highlighting, which runs each time the caret moves, so on every keystroke, asks once the text has been still for as long as highlighting waits after an edit (IntelliJ's autoreparse delay, 0.3 s by default). In code being edited, Cuttlebone answers from a copy with the broken lines blank, or else from the last version that type checked ("stale", shown only where nothing changes code; see Answers in code being edited).
  • In code Cuttlebone can't check (it doesn't parse there, or a declaration has an error), some features read the code themselves instead, so they don't go dark mid-edit; see Answers in code being edited for which. That reading doesn't infer types: a method call like x.to_string narrows to x's type when it's written or obvious (x: Foo, let x = Foo.new, self), and otherwise offers every to_string it can't rule out. It's never used where Cuttlebone has answered, or isn't running.
  • Find Usages and Rename search the IDE's word index for each name, then keep the occurrences Cuttlebone's textDocument/references lists: one check of the whole project, once per search, matched by the compiler's ids. For a declaration outside the project (the standard library, a dependency), a use of it in the project is found first, by resolving candidates a file at a time. A trait's method counts the calls to the methods implementing it, since the compiler gives each its own id, and renaming any of them renames them all. A compound assignment to a member (c.value += 1) calls the getter value and then the setter value=, so it's a use of both. Renaming a getter or setter offers to rename the other beside it too (as IntelliJ does for Java's), in the same rename, so every usage is found while the code still type checks; renaming only one lists such a site as a conflict. Where references have no answer (a file in the project doesn't parse or imports a module that doesn't exist, or code a declaration's error may affect mentions the name), each candidate is resolved through textDocument/definition instead, a file's requests sent together. Only there, in code Cuttlebone can't check, does Find Usages show what Parakeet's own reading finds, as possible usages; Rename leaves those alone and lists them as conflicts first. Usage counts, Safe Delete, Change Signature, Inline Method, Move and Introduce Parameter build on the same search (usage counts stop at 99+), and like Rename use fresh answers only. Moving files and directories, and renaming directories, make Cuttlebone's edits (workspace/willRenameFiles, asked before the move, from one check of the whole project) to every import of the modules that move, and to their uses by their own name; without its answer, the move lists that as a conflict.
  • Type Hierarchy is Cuttlebone's, from the type database: the traits a type implements, the traits a trait requires, and what requires or implements it.
  • Dependencies need syncing when dep/hash isn't the BLAKE3 hash of inko.pkg, which is how inko itself decides. Errors, indexing, and test runs never sync (inko would otherwise sync, and need the network, in each mirror): they use the project's dep/ as it is, and a banner says when it's out of date or was never synced.
  • Introduce and Inline Variable, Introduce Constant, Move Statement, and Smart Enter work from the file's tokens, following Inko's syntax reference: statements end at a line break unless the next line continues them (e.g. starts with .), and inlining parenthesizes values whose operators would otherwise bind differently (Inko's binary operators share one precedence and group left to right, and and/or bind more loosely). For the same reason, part of an operator chain (60 - 60 in total - 60 - 60) isn't offered as an expression. Inline Variable refuses a variable that's assigned again (=, :=, or a compound assignment like +=); one whose value calls a method, reads a field or makes an array, when it's used more than once (each use would make its own) or code runs before its last use (which may change it); and one whose value uses try, throw or return, into a closure (where they'd leave the closure). And Introduce Variable doesn't replace an occurrence that's assigned, past an assignment to what the expression reads, or in a loop that assigns it.
  • Change Signature, Introduce Parameter, Extract Method, and Inline Method rewrite calls from their tokens, following Inko's rules: no default arguments (so new parameters need a value for calls to pass), named arguments after positional ones, no empty parentheses, parameters that can't be reassigned, and owned values that move unless borrowed. They update the calls the search finds (above). Extract Method writes a local's type as written, else as the compiler has it (Cuttlebone's fresh hover at the local's name, as for "Specify type explicitly"), and leaves placeholders for types it can't work out, which inko check points out if left behind.
  • Quick fixes are Cuttlebone's (textDocument/codeAction), for the diagnostic IDs it fixes (invalid-symbol, unused-symbol, unused-result, invalid-assign, missing-field, invalid-implementation, missing-trait, unreachable): each one checked by Cuttlebone before it's offered, so it leaves fewer of its error and adds none, also in a file with other errors. They're asked for after highlighting, for the diagnostics on screen.
  • Implement and Override Methods stub a trait's required methods (those without a body) or default ones, with bodies that call panic('not implemented') (in Inko's prelude), which compiles for any return type. Go to Implementation and Super Method are Cuttlebone's: textDocument/implementation (what implements a trait or its method, from the type database), textDocument/declaration (the trait's method a type's method implements), and a type's traits from its type hierarchy. The trait gutter icons pointing down to implementations are Cuttlebone's too; those pointing up from an impl come from its header and the trait's declaration, as Implement Methods reads them (a request per method on every highlighting pass would be too many), and a type has no icon (⌘U finds its traits). Generate → Implement Trait leaves out the traits Cuttlebone says the type implements, in other modules too.
  • Style-guide inspections follow Inko's style guide where inko fmt doesn't. Removing an argument name is only offered where it keeps the meaning (the argument is in that parameter's position, after only positional ones), which needs the call to resolve to one method.
  • Tests run the way inko test does (from the project root), but through a program Parakeet generates that reports each test to the IDE. It's built in a mirror of the project in the IDE's system directory (with its own build cache), so nothing is added to your project. It takes one name filter, so rerunning failed tests builds it once and runs it once per failed test.
  • Errors across the whole project come from Cuttlebone's workspace pull, which it answers when a check (after an edit, or when it starts) changes a file's errors; Parakeet asks again as soon as it has an answer.
  • Intentions work from the file's tokens with the same operator rules as the refactorings. if ⇄ match only rewrites comparisons of a name or field to literals (so the value is the same each time it's read), and argument names are only removed when each named argument is in its parameter's position, since Inko accepts named arguments in any order.
  • Expression Type uses Cuttlebone's type when hover's range is exactly the chosen expression. "Specify type explicitly" writes the compiler's type for the variable, and "Remove explicit type" is offered only when the value says its type itself (a literal, a cast, Point(…), or Type.method returning that type, as Cuttlebone's type hints count it), so the annotation can't be what decides it, and the compiler's type for the value is the one written. Both use fresh answers only, since they write code, and types the compiler couldn't infer fully (with a ?) never.
  • Move rewrites imports from the usages Find Usages finds, and adds the imports the moved code needs the same way imports on paste does: copying records the imported and module-level names the copied code uses, and pasting adds the ones the target module lacks.
  • Indentation, Unwrap, and postfix completion come from the file's own tokens, so they follow unsaved edits and work without inko.

Requirements

  • A JetBrains IDE, version 2025.3 or newer

  • The bundled TextMate Bundles plugin enabled (it is by default)

  • The inko executable. By default the plugin uses inko on PATH, then ivm's default version. To pick an ivm-installed version or another executable for a project, use Settings → Languages & Frameworks → Inko.

  • Cuttlebone 0.15.0 or newer, for the compiler's types and definitions: Parakeet offers to download it

Installing

Parakeet is published on GitHub Releases, with a plugin repository your IDE can check for updates:

  1. In your IDE, open Settings → Plugins, click ⚙️, and choose Manage Plugin Repositories…
  2. Click +, enter https://github.com/setdef/Parakeet/releases/latest/download/updatePlugins.xml, and click OK.
  3. In the Marketplace tab, search for Parakeet for Inko, and install it.

The IDE then offers updates like any other plugin.

Alternatively, download parakeet-<version>-signed.zip from Releases and use Settings → Plugins → ⚙️ → Install Plugin from Disk… (without automatic updates). To try a local build, run ./gradlew buildPlugin and install build/distributions/parakeet-<version>.zip the same way.

Cuttlebone

Cuttlebone is a language server for Inko that type checks with Inko's own compiler. Parakeet requires it for everything that needs the compiler's types; without it, those features are unavailable rather than guessed.

Setting it up

Open an Inko file. With no Cuttlebone (none set, none on PATH, none downloaded), Parakeet offers to download one, in one notification: the build of the newest Cuttlebone release for your Inko (inko --version, from the project's toolchain), OS and CPU. Click Download Cuttlebone and it's downloaded, checked and started; nothing is downloaded without that click. Parakeet then starts one cuttlebone per project, through IntelliJ's own LSP support, and stops it when the project closes.

Cuttlebone has builds for macOS 15 or newer and Linux, on ARM64 and x86-64. Each build works with one Inko version, the compiler it links. When the newest release has none for your Inko, Parakeet looks for an older release from 0.15.0 on that has one (in Cuttlebone's release index), and if none does, says so instead of offering a build that wouldn't run.

  • Downloads come from Cuttlebone's GitHub releases, through the IDE's own HTTP support (so its proxy settings apply), with progress and a cancel button. Each is checked against its release's manifest.json (size and SHA-256), unpacked, and run once (cuttlebone --version must name the Inko it's for) before it's moved into place, in one step, in JetBrains' shared data directory (parakeet/cuttlebone/, one directory per Cuttlebone and Inko version, used by each of your JetBrains IDEs). A failed, cancelled or partial download is never used.
  • When your Inko changes (ivm switched versions, say, noticed when you come back to the IDE), Parakeet uses the download for the new version, or offers to download it the same way.
  • Updates are offered, never installed for you: when a newer Cuttlebone has a build for your Inko, Parakeet says so once.

To use a Cuttlebone of your own, put cuttlebone on your PATH (Parakeet also checks Cargo's bin directory, where cargo install puts it), or set Settings → Languages & Frameworks → Inko → Cuttlebone → Executable. The setting comes first, then PATH, and either takes precedence over a download. A cuttlebone on PATH or in Cargo's bin directory that doesn't run, or whose --version doesn't print a Cuttlebone version (a mise shim for a tool that isn't installed, say), or that's older than 0.15.0, isn't counted: Parakeet skips it, goes on to the next one or a download, and says so on the settings page. One that old in the setting is refused, saying why, and an older download is left unused, with the newer one offered as for none. Building Cuttlebone yourself needs Rust and LLVM 18 (its README says how), and it refuses any Inko but the one it links: cuttlebone --version prints it (e.g. cuttlebone 0.10.0 (Inko 0.21.1)).

The settings page says which Cuttlebone is in use and where it came from (the setting, PATH, Cargo's bin directory, or a download), whether it's running, and if not, why: not found, a different Inko version, or stopped after crashing. Beyond the one notification when there's no Cuttlebone at all, or none that will run (with the download offer, when there's one), Parakeet says so there (and in idea.log) and nowhere else. It restarts a crashed Cuttlebone up to 5 times in a row, waiting 1, 2, 4, 8 and 16 s, and IntelliJ's language services widget in the status bar can restart it by hand.

What asks Cuttlebone

Without Cuttlebone, every feature below is unavailable. With it:

Feature Cuttlebone's answer Where Cuttlebone can't check the code being edited
Go to definition, and what's built on resolving a name (quick documentation, hierarchies, inspections, intentions, refactorings, …) textDocument/definition: the declaration the compiler resolves for navigation, documentation and hierarchies, a stale answer, else Parakeet's reading of the code; for what changes code (refactorings, intentions, quick fixes, imports), nothing
Find Usages, Rename, usage counts, and the refactorings built on them textDocument/references, once per search, for the whole project; a trait's method with its implementations' each candidate's textDocument/definition, a file's at once; Find Usages alone adds Parakeet's reading, as possible usages, and Rename lists them as conflicts
Call Hierarchy callHierarchy/incomingCalls (from one check of the whole project, by the compiler's ids) and outgoingCalls, which Parakeet asks for (IntelliJ 2025.3's LSP support has none) a stale answer for callees; none for callers
Type Hierarchy typeHierarchy/supertypes and subtypes, from the type database, which Parakeet asks for (IntelliJ 2025.3's LSP support has none) none
Go to Implementation and Super Method, trait gutter icons to implementations, a trait method's implementations in Find Usages and Rename textDocument/implementation and textDocument/declaration, and a type's supertypes, which Parakeet asks for (IntelliJ 2025.3's LSP support has none); Rename takes fresh answers only none; Rename lists it as a conflict
Moving files and directories, and renaming directories workspace/willRenameFiles, asked before the move and made after it (IntelliJ 2025.3's LSP support has none) none: a conflict says so
Errors and warnings, Inspect Code, Optimize Imports' unused imports textDocument/diagnostic: the same as inko check, from a check of the documents as they are Cuttlebone's: it checks the code as it is
Quick fixes textDocument/codeAction: each fix checked with its edits applied, which Parakeet asks for itself, for the errors it shows Cuttlebone's: a fix is offered where it leaves fewer of its error and adds none
Errors across the whole project (the Project view, Problems) workspace/diagnostic, a long poll Cuttlebone's
Completion textDocument/completion on the real buffer: members, names with what to import, keywords, fields, and an import's modules and symbols Parakeet's reading: locals and parameters, keywords, the file's fields, members by name
Parameter info textDocument/signatureHelp: the resolved method's signature, or a type's constructor's (Point(x: 1), from its fields), with the receiver's type arguments, and the argument's parameter (named ones too) Parakeet's reading, for methods
Parameter-name and type hints textDocument/inlayHint, once for the whole file none: hints only inform
Highlight usages textDocument/documentHighlight: the same symbol by the compiler's ids, with assignments as writes Parakeet's reading
Quick documentation the resolved definition, plus hover's type at the caret the definition resolved as for navigation (above)
Expression Type hover's type, when its range is the expression none, and it says why
"Specify type explicitly" and "Remove explicit type" hover's fresh type, of the variable or of a value that says its type itself not offered
Go to Type Declaration textDocument/typeDefinition, through IntelliJ's LSP support nothing
Go to Class and Go to Symbol workspace/symbol, through IntelliJ's LSP support Cuttlebone's: it reads declarations from the syntax
Code folding textDocument/foldingRange, which Parakeet's folding asks for (IntelliJ's own would restart all highlighting, inko check too, at each answer) Cuttlebone's: it folds from the tokens
Reformat Code textDocument/formatting, through IntelliJ's LSP support none: inko fmt refuses code that doesn't parse too
Extend Selection textDocument/selectionRange, which Parakeet's selection asks for (IntelliJ 2025.3's LSP support has none), waiting on the EDT up to 0.5 s: Cuttlebone answers from the tokens, without a check Cuttlebone's: it reads the tokens

Where a search for what a refactoring changes has no fresh answer from Cuttlebone for a name that may be a use (its check crashed, it ran out of time, or the code there is being edited), the refactoring refuses, listing where, rather than leave the name as it is.

Where Cuttlebone answers that there's nothing there (a name that doesn't resolve, say), or has no answer for another reason (it's starting, or a question ran out of time), nothing is shown: Parakeet's reading stands in only for code being edited.

Errors and warnings are Cuttlebone's, which Parakeet pulls itself rather than through IntelliJ's display of them, which would have no quick fixes, wouldn't be in Inspect Code, and wouldn't mark files that aren't open; so without Cuttlebone there are none. Every other LSP feature IntelliJ offers is turned off, so nothing shows twice. Cuttlebone answers from each open file's unsaved text, and from saved text for the rest. A file changed with no editor open (by a refactoring, or by undoing one) is saved right after the change, since Cuttlebone would read its old text from disk; until it is, Parakeet uses no answer from Cuttlebone.

Answers in code being edited

Since its fifth milestone, Cuttlebone answers in code that doesn't parse, or has an error in a declaration, where an answer still holds. It says how in each answer's status:

  • Fresh, from a repaired copy: the documents as they are, with the lines in the way blank. Parakeet uses these like any fresh answer.
  • Stale: from the last version that type checked, in text that hasn't changed since. Parakeet uses these only where an answer is shown and changes no code:
Feature Stale answer Shown as
Parameter-name and type hints used the hint's tooltip says "possibly outdated"
Parameter info used "(possibly outdated)" after the signature
Quick documentation's "Type here" used "(possibly outdated)" after the type
Expression Type used "(possibly outdated)" after the type
Highlight usages used as a fresh answer: there's nowhere to mark it
Go to Type Declaration used (IntelliJ's own) as a fresh answer
Go to definition, documentation, Call and Type Hierarchy used as a fresh answer, but not one Rename at the caret takes
Rename, the refactorings, intentions, quick fixes and imports not used: they change code from it as no answer: nothing
Completion not used: choosing an item inserts it (Cuttlebone never sends one) as no answer: Parakeet's reading

Where Cuttlebone has no answer in code being edited (the method being edited after the edit; a file importing a module that doesn't exist, before any good check, and since its eleventh milestone only what uses that import; a file importing a module that doesn't parse; and, since its eighth milestone, only the method or item with a declaration's error and those naming what it's in), the features in the table above read the code themselves. Elsewhere in a project with a declaration error, Cuttlebone's answers are fresh.

Development

Tools are managed with mise (mise install).

Command What it does
./gradlew runIde Launch a sandbox IDE with the plugin loaded
./gradlew test Run all headless IDE tests, including integration tests against your installed inko and cuttlebone (-PnoCuttlebone skips the latter)
./gradlew test -PunitOnly Run only the unit tests, which use a fake inko and a fake Cuttlebone and need nothing installed
./gradlew typingReplay Type code into a std module at a person's speed against your installed inko and cuttlebone, and fail if typing responsiveness regresses (the limits, whose times are scaled by the machine's load unless PARAKEET_TIMING_STRICT is set, as CI sets it, and then by PARAKEET_TIMING_SCALE, the runner's baseline); PARAKEET_REPLAY_PROJECT adds a project of your own
./gradlew verifyPlugin Check compatibility with the oldest and newest supported IDEs
./gradlew buildPlugin Build the installable zip
scripts/check.sh [--full] Every check a commit must pass (--full adds the real-world projects' tests and the Plugin Verifier); see AGENTS.md. Cuttlebone's integration tests use $CUTTLEBONE or cuttlebone on PATH, and are skipped (with a message) without one; --full requires it
./gradlew test -PrealWorld --tests '*.RealWorldProjectsIntegrationTest' Open real Inko projects against your installed inko and cuttlebone: Yorick Peterse's public projects and Inko's own (idoc, website), cloned beside this repository (../yorickpeterse/<name>, ../inko-lang/<name>) or under PARAKEET_FIXTURES. Each is copied and synced, and skipped (with a message) when it isn't there or doesn't type check; nothing may log an error, and a sample of names must resolve, with hovers, documentation and completion
./gradlew test -PrealWorld --tests '*.RealWorldRefactoringsIntegrationTest' On copies of the same projects, Find Usages of a sample of each kind of declaration must match Cuttlebone's references, and every refactoring, applied at a sample of sites, must leave the copy type checking with its tests passing, or refuse saying why (SESSION-real-world-refactorings.md). PARAKEET_SWEEP_ONLY=Rename,Inline runs only some, and PARAKEET_PROTOCOL=<dir> keeps, per project, what Cuttlebone was sent and answered, and the sources on disk as they changed
scripts/install-hooks.sh Run scripts/check.sh on what's staged before each commit

The files in bundles/inko/ are copied unchanged from inko-lang/vscode (v0.7.0, commit 46243e5). To update them, copy package.json, language-configuration.json, and syntaxes/inko.tmLanguage.json from a newer release.

CI and releases

GitHub Actions runs build.yml on every push to main and every pull request:

  • Build and unit tests (Linux): builds the plugin zip (uploaded as an artifact) and runs the unit tests, which use a fake inko.
  • Plugin Verifier (Linux): checks compatibility with the oldest and newest supported IDEs.
  • Integration tests on Linux and macOS, each against the newest Inko release and the oldest supported one (0.20.0), installed with ivm by ci/install-inko.sh. ivm builds Inko from source, so installed versions are cached. These skip Cuttlebone's integration tests.
  • Cuttlebone on Linux and macOS: the latest Cuttlebone release, downloaded through its manifest.json and checked against its size and SHA-256 by ci/install-cuttlebone.sh, with the newest Inko it has a build for. It runs the integration tests against it and the typing replay, whose timing limits count here (AGENTS.md): as written for the reference machine, scaled by how fast the runner measures a fixed baseline workload (inko check of std, by ci/timing-baseline.py, in the job's summary), never below 1. Run by hand with slow, every answer from Cuttlebone is held that many milliseconds, to see the limits fail. With real-world (every release, or run by hand) it also runs the real-world projects' sweeps on Linux, on the commits ci/real-world-projects.txt pins, and fails if any project is missing or can't sync.

Dependabot proposes weekly updates to Gradle dependencies and GitHub Actions.

Releasing

Pushing a tag like v0.11.0 (matching version in gradle.properties) runs release.yml: the full build above, with the real-world sweeps against the latest Cuttlebone, then it signs the plugin and creates a GitHub Release with the signed zip and updatePlugins.xml (the plugin repository file IDEs check for updates). If a PUBLISH_TOKEN secret exists, it also publishes to the JetBrains Marketplace.

One-time setup:

  1. Create a signing certificate (JetBrains' guide), kept outside the repository:

    openssl genpkey -aes-256-cbc -algorithm RSA -out private_encrypted.pem -pkeyopt rsa_keygen_bits:4096
    openssl rsa -in private_encrypted.pem -out private.pem
    openssl req -key private.pem -new -x509 -days 3650 -out chain.crt
    rm private.pem
  2. Add repository secrets (Settings → Secrets and variables → Actions, or gh secret set): CERTIFICATE_CHAIN (contents of chain.crt), PRIVATE_KEY (contents of private_encrypted.pem), and PRIVATE_KEY_PASSWORD.

Without the signing secrets, a release fails at its first step and publishes nothing.

To also publish to the JetBrains Marketplace later, upload the first version by hand at plugins.jetbrains.com (it only accepts automated uploads for plugins that already exist), then add a PUBLISH_TOKEN secret (a Marketplace token).

History

Audits and session reports, oldest first:

Icon

Parakeet's icons (a flat parakeet on a branch) are original to this plugin and not derived from Inko's logo, which is licensed CC BY-NC-SA 4.0 and isn't used here.

License

MPL-2.0, the same license as inko-lang/vscode, whose grammar and language configuration this plugin bundles.

About

Unofficial, community support for the Inko programming language in JetBrains IDEs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages