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.
Setup
Install VS Code Extension
- Open the Extensions view in VS Code via the
View -> Extensionsmenu - Search for "Naninovel" and click "Install"
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
- Make sure Naninovel is installed in the Unity project.
- 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.
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 auto-generated meta files, enable word wrap and disable word-based suggestions, occurrence highlighting and bracket pair colorization:
{
"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 by opening 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 extension 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:
"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",
"UnknownTag": "#5d6470",
"CommandTag": "#5d6470",
"ExpressionTag": "#5d6470",
"WaitInputTag": "#6cb2ed",
"SelectTag": "#5d6470",
"SelectOption": "#acb2be",
"ActorsCommandGroup": "#e2be7f",
"TextCommandGroup": "#93b6f8",
"PlaybackCommandGroup": "#a9e17e",
"BranchingCommandGroup": "#be93f3",
"VisualsCommandGroup": "#f88d84",
"AudioCommandGroup": "#9dd6de",
"UICommandGroup": "#f893b7"
}
}
},
"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" } },
{ "scope": ["naniscript.tag"], "settings": { "foreground": "#5d6470" } },
{ "scope": ["naniscript.select-option"], "settings": { "foreground": "#acb2be" } }
]
}
},"editor.semanticTokenColorCustomizations": {
"[*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",
"UnknownTag": "#acb5c6",
"CommandTag": "#acb5c6",
"ExpressionTag": "#acb5c6",
"WaitInputTag": "#257dc8",
"SelectTag": "#acb5c6",
"SelectOption": "#4b5871",
"ActorsCommandGroup": "#c79b4a",
"TextCommandGroup": "#6b95df",
"PlaybackCommandGroup": "#7fb857",
"BranchingCommandGroup": "#9d7ad4",
"VisualsCommandGroup": "#e07d74",
"AudioCommandGroup": "#6ab7c3",
"UICommandGroup": "#df79a5"
}
}
},
"editor.tokenColorCustomizations": {
"[*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" } },
{ "scope": ["naniscript.tag"], "settings": { "foreground": "#acb5c6" } },
{ "scope": ["naniscript.select-option"], "settings": { "foreground": "#4b5871" } }
]
}
},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.
Decorations
By default, semi-transparent icons are drawn instead of line identifiers to help distinguish between line and command types. You can tweak the behaviour with the Decoration Style and Decoration Opacity settings. The individual colors of the command groups can be adjusted in the semanticTokenColorCustomizations setting.
Auto-Completion
Completion suggestions appear automatically when typing symbols such as @, [ or {. To invoke them manually at any point, press Ctrl+Space; for example, on an empty line this lists the available characters to author a printed text line.
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:
- Open with
; > region name, where "region name" can be anything - 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 Story Editor, to provide helpful functions like auto-completion and diagnostics.
The metadata file is stored at .nani/Transient/Metadata.json under the NaninovelData auto-generated folder. When Auto Generate Metadata is enabled in the engine configuration, the metadata is regenerated 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:
public class DefaultMetadataProvider : IMetadataProvider
{
public virtual Project GetMetadata ()
{
var meta = new Project();
var cfg = ProjectConfigurationProvider.LoadOrDefault<ScriptsConfiguration>();
meta.EntryScript = cfg.StartGameScript;
meta.TitleScript = cfg.TitleScript;
Notify("Processing commands...", 0);
meta.CommandGroups = MetadataGenerator.GenerateCommandGroupMetadata();
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 queries...", .95f);
meta.Queries = MetadataGenerator.GenerateQueryMetadata();
Notify("Processing enums...", .99f);
meta.Enums = MetadataGenerator.GenerateEnumsMetadata();
meta.Symbols = new(Compiler.Symbols);
return meta;
}
protected 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 queries. 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:
[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 queries and predefined variables, use the ExpressionContext attribute:
[ExpressionContext]
public StringParameter Expression;To auto-complete with values from an arbitrary enumeration type, use the EnumContext attribute:
[EnumContext(typeof(PlatformID))]
public StringParameter Platform;To auto-complete and analyze usage and correctness of navigation endpoints (script path and label), use the EndpointContext attribute:
[EndpointContext]
public StringParameter Path;To auto-complete with a resource, use ResourceContext and provide a path prefix for the resources. The example below will complete with sound effect resources:
[ResourceContext(AudioConfiguration.DefaultSfxPathPrefix)]
public StringParameter Audio;To auto-complete with an actor ID (of any type), use the ActorContext attribute:
[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:
[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:
[ActorContext(CharactersConfiguration.DefaultPathPrefix)]
public StringParameter CharacterId;
[AppearanceContext]
public StringParameter CharacterAppearance;Except for EndpointContext, each of the above context attributes allows providing an optional index 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 primary parameter of the @char command):
[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:
[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 query parameters to enable auto-completion and diagnostics in the IDE extension. See an example in the queries guide.
Enum Expressions
When using the EnumContext IDE attribute, instead of an enum it's possible to specify an expression to be evaluated by the IDE to produce an enum name based on command parameter values or other variables, such as the title script path.
Expression syntax:
- Evaluated parts should be wrapped in curly braces (
{}) - To reference the path of the
Start Game ScriptorTitle Scriptassigned in the scripts configuration, use$EntryScriptor$TitleScript, respectively - 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 component of a named value (0 for name and 1 for value) - Use null coalescing (
??) after a parameter reference for a fallback if the value is not specified - Use single quotes to specify literal text, eg as a fallback value:
{:Id??'MainBackground'} - Use the concatenation operator (
+) to merge values from multiple enums
For example, consider the expression assigned to a named parameter of a custom command, assuming the Quests/... enums are added via a custom metadata provider:
[EnumContext("Quests/{:QuestId[0]??$EntryScript}", 1)]
public NamedStringParameter QuestId;When the name component of the parameter is assigned foo, it will evaluate to Quests/foo; otherwise, given the Start Game Script path is bar, it will evaluate to Quests/bar.
Another example, applied to the @char command for character poses:
[EnumContext("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.
Enum expressions combined with custom metadata providers allow creating flexible auto-completion 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.