Skip to main content

Project Script Completions

The LSP automatically indexes your Ignition project directory and provides intelligent completions for project.* and shared.* script references, including functions, classes, and variables defined in your project scripts.

How It Works

When you open an Ignition project directory, the LSP scans all Python scripts and builds an index of:

  • Module paths (project.library.utils, shared.common.helpers)
  • Functions and their signatures
  • Classes and their methods
  • Module-level variables

This enables cross-file completions and go-to-definition for your entire project.

Project Structure

Ignition organizes scripts in a hierarchical structure:

ignition/
└── script-python/
├── project/ # Project-scoped scripts
│ └── library/
│ ├── code.py # project.library
│ └── utils/
│ └── code.py # project.library.utils
└── shared/ # Shared across projects
└── common/
└── code.py # shared.common

Module Path Completions

Type project. or shared. to see available modules:

# Type 'project.' to see:
project.library.
project.alarms.
project.database.

# Navigate deeper:
project.library.utils.
project.library.config.

The LSP shows:

  • Packages - Modules with children (e.g., project.library)
  • Leaf modules - Final scripts with symbols (e.g., project.library.utils)

Function Completions

After selecting a module, get completions for all functions defined in that script:

# In project/library/utils/code.py:
def validate_tag_path(path):
"""Check if tag path is valid."""
return path.startswith("[")

def format_timestamp(ts):
"""Format timestamp for display."""
return system.date.format(ts, "yyyy-MM-dd HH:mm:ss")

# In another script:
from project.library import utils

utils. # <- See: validate_tag_path(), format_timestamp()

Each completion shows:

  • Function signature with parameter names
  • Docstring (if available)
  • Module source path

Class Completions

Access classes and their members:

# In project/library/models/code.py:
class AlarmConfig:
"""Configuration for alarm handling."""

def __init__(self, name, priority):
self.name = name
self.priority = priority

def to_dict(self):
return {"name": self.name, "priority": self.priority}

# In another script:
from project.library.models import AlarmConfig

config = AlarmConfig("HighTemp", 1)
config. # <- See: to_dict(), name, priority

Variable Completions

Module-level variables and constants are also available:

# In project/config/constants/code.py:
DATABASE_NAME = "production"
MAX_RETRIES = 3
TAG_PROVIDER = "[default]"

# In another script:
from project.config import constants

db = constants.DATABASE_NAME # <- Autocomplete available

Nested Packages

Navigate through deeply nested project structures:

# Deep hierarchy:
project.
production.
lines.
line1.
controls.
temperature. # <- All levels indexed

Go-to-Definition

Use LSP go-to-definition (typically gd) on any project reference to jump to the actual implementation:

from project.library import utils

# Press 'gd' on 'utils' -> Jump to project/library/utils/code.py
# Press 'gd' on 'validate_tag_path' -> Jump to function definition
result = utils.validate_tag_path(tag)

Workspace Symbols

Search for any symbol across your entire project using LSP workspace symbols (:LspWorkspaceSymbols or telescope/fzf integration):

# Search: "validate"
→ validate_tag_path (project.library.utils)
→ validate_user (project.security.auth)
→ validate_config (shared.common.validation)

Symbol Cache

The LSP caches parsed symbols for performance:

  • On open: Scans all project scripts
  • On save: Updates symbols for the modified file
  • On refresh: Manual refresh via :LspRestart

Inheritance Tracking

The LSP understands class inheritance:

# In project/base/handler/code.py:
class BaseHandler:
def handle(self, event):
pass

# In project/alarms/handler/code.py:
from project.base.handler import BaseHandler

class AlarmHandler(BaseHandler):
def handle(self, event):
# Overrides base method
self.process_alarm(event)

# LSP knows AlarmHandler has both:
# - handle() (overridden)
# - Any methods from BaseHandler

Multi-Project Support

If your workspace contains multiple Ignition projects, each is indexed separately:

workspace/
├── ignition-project-1/
│ └── script-python/
│ └── project/ # project.* for project 1
└── ignition-project-2/
└── script-python/
└── project/ # project.* for project 2 (separate index)

Private Symbol Filtering

The LSP automatically filters out private symbols (names starting with _):

# In module:
def public_function(): # Shown in completions
pass

def _private_helper(): # Hidden from completions
pass

_INTERNAL_CONSTANT = 42 # Hidden from completions

Performance

The project scanner is optimized for large projects:

  • Async scanning - Non-blocking background indexing
  • Incremental updates - Only re-scans modified files
  • Symbol caching - In-memory cache for fast lookups
  • Lazy loading - Only parses files when needed

Troubleshooting

"No completions for project.*"

  1. Check that the LSP has scanned your project:

    :IgnitionInfo  # Look for "Project index: X scripts"
  2. Ensure you're in an Ignition project directory:

    workspace/
    └── ignition/
    └── script-python/ # LSP looks for this
  3. Restart the LSP to force a rescan:

    :LspRestart

"Completions are outdated"

The LSP updates symbols on save. If you modify a file outside Neovim:

:LspRestart  # Force full rescan

"Missing some modules"

Check the LSP log for scanning errors:

:lua vim.cmd.edit('/tmp/ignition-lsp.log')

Look for parse errors or permission issues.