Skip to content

IDE Extension

Code editor features such as syntax highlighting, error checking, auto-completion, and interactive documentation can significantly improve productivity. Naninovel has an official extension for VS Code ↗, offering rich authoring tools for working with scenario scripts.

cover
cover

Setup

Install VS Code Extension

  1. Open the Extensions view in VS Code via View -> Extensions menu
  2. Search for "Naninovel" and click "Install"
cover

NOTE

The extension in the VS Code registry is compatible with the current stable Naninovel release. When using a preview release of Naninovel, switch to the pre-release extension stream. When using a final Naninovel release, disable auto-update in VS Code and install the associated legacy version.

Activate the Extension

  1. Make sure Naninovel is installed in the Unity project.
  2. Open the Assets folder of the Unity project in VS Code.

When the extension detects a .nani file in the current workspace, it will activate the LSP service. The service handles tasks such as script diagnostics, auto-completion, and indicating which script line is currently playing.

cover

Workspace Root

Naninovel generates project metadata and bridging files required for communication with the VS Code extension under the generated data directory (Assets/NaninovelData by default). This means that when opening Naninovel projects in VS Code (selecting the workspace root ↗), you need to select a folder that includes the generated data directory at some level.

Some users, however, prefer to open only the folder containing the scenario scripts, which doesn’t include the generated data directory. In such cases, move the NaninovelData folder into the scenario scripts folder to make it visible to VS Code.

Restart VS Code after moving the folder for the changes to take effect.

VS Code Settings

Below are the recommended settings for VS Code to ignore Unity's autogenerated meta files, enable word wrap and spell checking (given the spell check extension ↗ is installed), and disable word-based suggestions:

json
{
    "files.exclude": {
        "**/*.meta": true
    },
    "editor.wordWrap": "on",
    "editor.wordBasedSuggestions": "off",
    "editor.occurrencesHighlight": "off",
    "editor.suggest.showWords": false,
    "editor.bracketPairColorization.enabled": false
}

You can access the settings JSON file via File -> Preferences -> Settings and clicking the "Open Settings (JSON)" button in the upper-right corner of the window. Select the "User" tab to edit settings for all projects or "Workspace" to affect only the current project containing the scenario scripts.

Some of the above settings are applied by default when the package is installed, but you can override them if you wish. If you'd like to also customize syntax highlighting, add the following and tweak the colors:

json
"editor.semanticTokenColorCustomizations": {
    "[*Dark*][*Night*][*Abyss*][*Monokai*]": {
        "enabled": true,
        "rules": {
            "CommentLine": "#5d6470",
            "CommentText": "#5d6470",
            "LabelLine": "#9bc37c",
            "LabelText": "#9bc37c",
            "CommandLine": "#6cb2ed",
            "InlinedCommand": "#6cb2ed",
            "Command": "#6cb2ed",
            "CommandIdentifier": "#6cb2ed",
            "Parameter": "#cd9769",
            "ParameterIdentifier": "#cd9769",
            "ParameterValue": "#e2be7f",
            "LocalizableValue": "#acb2be",
            "EndpointValue": "#9bc37c",
            "GenericTextLine": "#acb2be",
            "GenericTextPrefix": "#e2be7f",
            "GenericTextAuthor": "#e2be7f",
            "GenericTextAuthorAppearance": "#e2be7f",
            "Expression": "#62b8c1",
            "TextIdentifier": "#5d6470",
            "WaitFlag": "#6cb2ed",
            "Error": "#d14e4e"
        }
    },
    "[*Light*][*Day*][*Bright*]": {
        "enabled": true,
        "rules": {
            "CommentLine": "#acb5c6",
            "CommentText": "#acb5c6",
            "LabelLine": "#51a612",
            "LabelText": "#51a612",
            "CommandLine": "#257dc8",
            "InlinedCommand": "#257dc8",
            "Command": "#257dc8",
            "CommandIdentifier": "#257dc8",
            "Parameter": "#c642a5",
            "ParameterIdentifier": "#c642a5",
            "ParameterValue": "#9250bf",
            "LocalizableValue": "#4b5871",
            "EndpointValue": "#51a612",
            "GenericTextLine": "#4b5871",
            "GenericTextPrefix": "#9250bf",
            "GenericTextAuthor": "#9250bf",
            "GenericTextAuthorAppearance": "#9250bf",
            "Expression": "#3abfb3",
            "TextIdentifier": "#acb5c6",
            "WaitFlag": "#257dc8",
            "Error": "#be2222"
        }
    }
},
"editor.tokenColorCustomizations": {
    "[*Dark*][*Night*][*Abyss*][*Monokai*]": {
        "textMateRules": [
            { "scope": ["naniscript.comment"], "settings": { "foreground": "#5d6470" } },
            { "scope": ["naniscript.label"], "settings": { "foreground": "#9bc37c" } },
            { "scope": ["naniscript.command"], "settings": { "foreground": "#6cb2ed" } },
            { "scope": ["naniscript.command.parameter.id"], "settings": { "foreground": "#cd9769" } },
            { "scope": ["naniscript.command.parameter.value"], "settings": { "foreground": "#e2be7f" } },
            { "scope": ["naniscript.generic-text"], "settings": { "foreground": "#acb2be" } },
            { "scope": ["naniscript.author"], "settings": { "foreground": "#e2be7f" } },
            { "scope": ["naniscript.expression"], "settings": { "foreground": "#62b8c1" } },
            { "scope": ["naniscript.text-identifier"], "settings": { "foreground": "#5d6470" } }
        ]
    },
    "[*Light*][*Day*][*Bright*]": {
        "textMateRules": [
            { "scope": ["naniscript.comment"], "settings": { "foreground": "#acb5c6" } },
            { "scope": ["naniscript.label"], "settings": { "foreground": "#51a612" } },
            { "scope": ["naniscript.command"], "settings": { "foreground": "#257dc8" } },
            { "scope": ["naniscript.command.parameter.id"], "settings": { "foreground": "#c642a5" } },
            { "scope": ["naniscript.command.parameter.value"], "settings": { "foreground": "#9250bf" } },
            { "scope": ["naniscript.generic-text"], "settings": { "foreground": "#4b5871" } },
            { "scope": ["naniscript.author"], "settings": { "foreground": "#9250bf" } },
            { "scope": ["naniscript.expression"], "settings": { "foreground": "#3abfb3" } },
            { "scope": ["naniscript.text-identifier"], "settings": { "foreground": "#acb5c6" } }
        ]
    }
}

The semanticTokenColorCustomizations colors are applied to the LSP context (script content after the extension is activated), while tokenColorCustomizations are applied to the TextMate context (snippets in the tooltips and scripts before the extension is activated).

TIP

Find the full settings applied by default in the package sources ↗ under configurationDefaults.

Folding

The following constructs get folding support by default:

  • Labels (until another label)
  • Consecutive comment lines
  • Indented (nested) blocks

You can also specify custom folding regions via comments with this syntax:

  1. Open with ; > region name, where "region name" can be anything
  2. Close with ; < region name, where "region name" equals the opening name

Project Metadata

The Naninovel metadata is a JSON file that contains various information associated with the authored project: available characters, backgrounds, resources, commands, etc. This information is used by authoring tools, such as the IDE extension and web editor, to provide helpful functions like auto-completion and diagnostics.

The metadata file is stored at .nani/Metadata.json under the NaninovelData auto-generated folder. When Auto Generate Metadata is enabled in the engine configuration, the metadata is re-generated automatically on domain reload and after editing Naninovel configuration or resource assets. To manually update metadata, use the Naninovel -> Update Metadata editor menu or the Ctrl + Shift + U hotkey.

TIP

If the metadata is not syncing, make sure Enable Bridging is turned on in the engine configuration and ensure the Generated Data Root value shown at the top of the engine configuration menu equals the data root reported by the IDE extension.

Metadata Provider

To fill generated metadata with additional custom values or override defaults, create a C# class that implements the IMetadataProvider interface; the implementation should have a parameterless constructor. When found, a custom provider will be used instead of the default one each time project metadata is generated.

Below is the default metadata provider, which you can use as a reference when implementing your own:

csharp
public class DefaultMetadataProvider : IMetadataProvider
{
    public Project GetMetadata ()
    {
        var meta = new Project();
        var cfg = ProjectConfigurationProvider.LoadOrDefault<ScriptsConfiguration>();
        meta.EntryScript = cfg.StartGameScript;
        meta.TitleScript = cfg.TitleScript;
        Notify("Processing commands...", 0);
        meta.Commands = MetadataGenerator.GenerateCommandsMetadata();
        Notify("Processing resources...", .25f);
        meta.Resources = MetadataGenerator.GenerateResourcesMetadata();
        Notify("Processing actors...", .50f);
        meta.Actors = MetadataGenerator.GenerateActorsMetadata();
        Notify("Processing variables...", .75f);
        meta.Variables = MetadataGenerator.GenerateVariablesMetadata();
        Notify("Processing functions...", .95f);
        meta.Functions = MetadataGenerator.GenerateFunctionsMetadata();
        Notify("Processing constants...", .99f);
        meta.Constants = MetadataGenerator.GenerateConstantsMetadata();
        meta.Syntax = Compiler.Syntax;
        return meta;
    }

    private static void Notify (string info, float progress)
    {
        if (EditorUtility.DisplayCancelableProgressBar("Generating Metadata", info, progress))
            throw new OperationCanceledException("Metadata generation cancelled by the user.");
    }
}

IDE Attributes

Naninovel provides several C# attributes ↗ to enable IDE-related functionality for custom commands and expression functions. For example, to add on-hover documentation to custom commands and/or parameters, apply the Doc attribute to the command type and to parameter fields, respectively:

csharp
[Doc("Summary of the custom command.")]
public class CustomCommand : Command
{
    [Doc("Summary of the custom parameter.")]
    public StringParameter CustomParameter;
}

To make a parameter support auto-completion with both built-in and custom expression functions and pre-defined custom variables, use the ExpressionContext attribute:

csharp
[ExpressionContext]
public StringParameter Expression;

To auto-complete with values from an arbitrary enumeration type ↗ use the ConstantContext attribute:

csharp
[ConstantContext(typeof(PlatformID))]
public StringParameter Platform;

To auto-complete and analyze usage and correctness of navigation endpoints (script path and label) use the EndpointContext attribute:

csharp
[EndpointContext]
public NamedStringParameter Goto;

To auto-complete with a resource, use ResourceContext and provide a path prefix for the resources. The example below will complete with audio resources:

csharp
[ResourceContext(AudioConfiguration.DefaultAudioPathPrefix)]
public StringParameter Audio;

To auto-complete with an actor ID (of any type) use the ActorContext attribute:

csharp
[ActorContext]
public StringParameter ActorId;

To auto-complete with an actor ID of a specific type, use ActorContext with the first argument specifying the path prefix of the actor resources. The example below will complete with printer IDs:

csharp
[ActorContext(TextPrintersConfiguration.DefaultPathPrefix)]
public StringParameter PrinterId;

To auto-complete appearances of an actor with an ID specified in the same or another parameter in the current command, use AppearanceContext. Note that this requires ActorContext to be specified in the same command:

csharp
[ActorContext(CharactersConfiguration.DefaultPathPrefix)]
public StringParameter CharacterId;
[AppearanceContext]
public StringParameter CharacterAppearance;

Each of the above attributes allows providing an optional namedIndex argument. Use it with named parameters to specify which part of the parameter value the attribute applies to. The example below will allow auto-completing the name part of the named parameter with character IDs and the value part with appearances for the currently typed character (similar to the nameless parameter of the @char command):

csharp
[ActorContext(CharactersConfiguration.DefaultPathPrefix, 0), AppearanceContext(1)]
public NamedStringParameter IdAndAppearance;

The parameter context attributes can be applied to a class instead of fields to specify (or override) the contexts for fields declared in parent classes. For example, while the Id parameter is declared in the abstract ModifyActor command, the context is applied to the ModifyBackground derived class:

csharp
[ActorContext(BackgroundsConfiguration.DefaultPathPrefix, paramId: "Id")]
public class ModifyBackground : ModifyActor { }

You can use the same approach when inheriting custom commands from built-in ones. Don't forget to provide the optional paramId argument when applying a parameter context attribute to a class instead of a field.

TIP

Most of the same parameter context attributes can be applied to expression function parameters to enable auto-completion and diagnostics in the IDE extension. See an example in the functions guide.

Constant Expressions

When using the ConstantContext IDE attribute, instead of an enum it's possible to specify an expression to be evaluated by the IDE to produce a constant name based on command parameter values or other variables, such as the currently inspected script.

Expression syntax:

  • Evaluated parts should be wrapped in curly braces ({})
  • To reference the currently inspected script path, use $Script
  • To reference a parameter value, use : followed by the parameter ID (field name as specified in C#, not alias)
  • Use [0] or [1] after a parameter reference to specify the named value (0 for name and 1 for index)
  • Use null coalescing (??) after a parameter reference for a fallback if the value is not specified
  • Use the concatenation operator (+) to merge values from multiple constants

For example, check the expression assigned to the Path parameter of the built-in [@goto] command:

csharp
[ConstantContext("Labels/{:Path[0]??$Script}", 1)]
public NamedStringParameter Path;

When the name component of the parameter is assigned foo, it will evaluate to Labels/foo; otherwise, given the inspected script path is bar, it will evaluate to Labels/bar.

Another example for character poses applied to the @char command:

csharp
[ConstantContext("Poses/Characters/{:Id??:IdAndAppearance[0]}+Poses/Characters/*", paramId: nameof(Pose))]
public class ModifyCharacter { ... }

This will merge shared character poses with poses for characters whose ID is assigned to the "Id" parameter or (when not assigned) the name component of the "IdAndAppearance" parameter.

Constant expressions combined with custom metadata providers allow creating flexible autocompletion scenarios for the IDE extension.

Other IDEs and Editors

If you're using a VS Code–compatible editor such as VSCodium ↗, Cursor ↗ or Trae ↗, install our extension from the Open VSX registry: open-vsx.org/extension/elringus/naninovel ↗.

While we don't maintain extensions for other editors, we have an LSP-compliant ↗ language server available in the engine monorepo ↗. The server is implemented in C#, can be compiled to WASM and has built-in JavaScript bindings, making it usable in most modern IDEs.

Our VS Code extension is built on top of the same language server. The extension sources are available in the monorepo as well — feel free to use them as a reference when integrating the server into your IDE of choice. To access the repository, register your license ↗.

Alternatively, if you're using an editor with TextMate grammar support (for example, Sublime ↗ or Visual Studio ↗), we provide one here: textmate.json ↗. Note that the grammar is only usable for syntax highlighting; the language server is still required for other IDE features.