Skip to main content
This page covers how a plugin works with files:
  • File actions (file-actions.wit, API 1.3): quick fixes and refactorings that create, rename or delete files, applied as one undoable workspace edit.
  • Project files (workspace.wit): read, write, find and search files inside the open projects.
  • Other files (fs.wit, API 1.3): files outside the projects, under explicit grants, and the plugin’s own private directory.
  • File templates: entries of the File › New… menu, no code needed.

File actions

A file action is a code action whose result is file operations plus text edits. Declare the code-actions provider kind (the same kind as text-only quick fixes), implement openidea_plugin::file_actions::FileActions, and register it together with providers:
The actions are listed with the text-only code actions under Show Context Actions (⌥⏎ / Alt+Enter) and have the same 50 ms editor budget.

The FileAction record

Rules:
  • Operations and edits are one workspace edit: if any step fails nothing is applied, and one Undo reverses all of it. Undoing a Delete restores the file.
  • Create fails when the file exists unless overwrite; missing parent directories are created.
  • Paths are absolute or relative to the project root and must stay inside it; a path outside disables the action.

Resolving on pick

Return an action with data and empty operations and edits to defer the work. When the user picks it, the host calls resolve_file_action(doc, action) under the 5-second command budget instead of 50 ms. As with resolve_code_action, host::pick, pick_many, input and confirm ask in the editor’s window and the wait doesn’t count. Return Error::Cancelled or an action with nothing to apply to abandon it.

Project files

Inside the open projects, a plugin uses its Project handle (ctx.project() in commands and events, host::project_of(path) in providers, host::projects() for every open project). All of these need project.read except write_file: Paths are absolute or relative to the root. .. and symlinks that leave the root are refused. In a remote project these run on the server, next to the files. Two related calls need no capability and work only from a command or event of a window: host::pick_directory(title, initial) (API 1.3) opens the native folder chooser, and host::open_project(path, new_window) (API 1.3) runs the normal File › Open… flow, including the trust prompt. Paths belong to the UI machine.

Files outside the projects

host::fs (WIT fs.wit, API 1.3) reads and writes files outside the open projects: the configuration of a tool the plugin integrates with, a licence file, a program installed elsewhere. Every call is checked against a grant:
  • Paths are absolute or start with ~/. A grant covers a directory (any depth) or one file. The path is resolved to its real location before comparing, so a symlink out of the granted directory is refused, and /opt/see doesn’t cover /opt/seeyon.
  • Granting a whole file system (/, C:\) is refused when the manifest is read.
  • fs.write doesn’t imply fs.read. Writes fail with Error::Invalid when the directory is missing, the text can’t be encoded, or an editor holds unsaved changes for the file.
  • Both are high-risk capabilities: the install page tells the user so.

The private directory

host::fs::plugin_dir() returns a directory that belongs only to this plugin, separate from its installed code and its key-value storage. Reading and writing inside it needs no fs.* or project.read grant, even during activation or provider calls without a project. It is created owner-only (0700 on Unix, an owner-only ACL on Windows), survives idle reclaim, reloads, updates and restarts, and is removed on uninstall. Use it for generated content or caches larger than storage. A path in it works with host::open_location and as an argument to an approved process.

File templates

[[contributes.fileTemplates]] adds entries to the File › New… menu. They need no code and grant no file access: files are created only when the user invokes a template, through the IDE’s normal New File flow.
With engines.openidea = "^1.3", fileName creates an exact file name (dotfiles too) without a name prompt, and variables add required text fields to the new-file dialog, inserted with $NAME$ or ${NAME}:
Variable names are unique ASCII identifiers and can’t replace built-in variables (NAME, PACKAGE, END, …). $PACKAGE$ follows the built-in rules for the extension (source roots for Java and Kotlin, Composer PSR-4 for PHP, …).