Tortoise Language Server is a Kotlin implementation of the Language Server Protocol for the Turtle Academy LOGO dialect. It provides editor features for .logo files through LSP4J over stdio, with IntelliJ IDEA plus the LSP4IJ plugin as the primary client.
The project is intentionally scoped as a single-file language server. Every editor feature is built on the same semantic pipeline: lexing, tolerant parsing, AST construction, symbol collection, scope resolution, diagnostics, and protocol mapping.
- Project Status
- Prerequisites
- Build And Run
- Connect To An LSP Client
- Usage
- Supported Language Subset
- Features
- Architecture
- Project Layout
- Testing
- Limitations And Non-Goals
- License
This repository contains the core LSP implementation for a focused Turtle Academy LOGO tooling project. The current feature set is deliberately small and reviewable:
- Semantic tokens
- Go-to-declaration
- References
- Diagnostics
- Completion
The server does not try to become a full LOGO IDE. It favors correctness, deterministic behavior, clear package boundaries, and a semantic core that can be defended during code review.
Before building or running the server, install:
- JDK 17 or newer. The Gradle build uses the Kotlin JVM toolchain set to Java 17.
- A JetBrains IDE for the documented client setup, such as IntelliJ IDEA.
- The LSP4IJ plugin from the JetBrains Marketplace.
The Gradle wrapper is included, so a separate Gradle installation is not required.
From the project root run:
-
Windows:
.\gradlew.bat build
-
macOS/Linux:
./gradlew build
installDist creates a runnable distribution under build/install/tortoise-ls.
-
Windows:
.\gradlew.bat installDist
-
macOS/Linux:
./gradlew installDist
For quick local checks, the application plugin can run the server directly:
-
Windows:
.\gradlew.bat run
-
macOS/Linux:
./gradlew run
For an editor client, prefer the generated launcher from installDist:
-
Windows:
build\install\tortoise-ls\bin\tortoise-ls.bat
-
macOS/Linux:
./build/install/tortoise-ls/bin/tortoise-ls
The server uses stdio transport. When started by an LSP client, the client owns the process and communicates with it over standard input and output.
Any LSP-compatible client can start the server as a stdio process. The documented path is IntelliJ IDEA with LSP4IJ.
For the general LSP4IJ user-defined language server flow, see the official User-defined language server documentation.
First run installDist, then open:
Settings > Languages & Frameworks > Language Servers
Create a new user-defined language server with the following values.
- Name:
tortoise-ls - Command:
- Windows: absolute path to
tortoise-ls\build\install\tortoise-ls\bin\tortoise-ls.bat - macOS/Linux: absolute path to
tortoise-ls/build/install/tortoise-ls/bin/tortoise-ls
- Windows: absolute path to
Add one file name pattern mapping:
- File name pattern:
*.logo - Language ID:
logo
Leave the other fields empty unless explicit server configuration is added later.
After the mapping is active, opening a .logo file should start the language server automatically. In the editor, supported LOGO files should expose semantic token information, diagnostics in the editor/problems view, completion suggestions, and navigation actions such as go-to-declaration and find references for supported symbols.
The exact colors are controlled by IntelliJ, LSP4IJ, and the active theme. For example, several semantic token categories may appear with the same color, and some built-ins may not receive a visibly distinct style even though the server classifies them.
IntelliJ's spelling inspection is separate from LSP diagnostics and semantic tokens. The server classifies LOGO words such as localmake as keywords, but an LSP4IJ user-defined language server does not provide IntelliJ with a LOGO-specific PSI tree or SpellcheckingStrategy. As a result, IntelliJ can still underline LOGO keywords or built-ins as typos.
There are two practical workarounds:
- Add
docs/intellij-logo-dictionary.dicunderSettings > Editor > Natural Languages > Spellingso IntelliJ accepts Turtle Academy LOGO keywords and built-ins. - Create an inspection scope for
*.logofiles and lower or disableSettings > Editor > Inspections > Proofreading > Spellingfor that scope.
JetBrains' plugin API handles the product-grade version of this through a custom language plugin that registers a SpellcheckingStrategy and returns EMPTY_TOKENIZER for non-natural-language PSI elements such as keywords, built-ins, punctuation, and numbers. That is outside this repository's current LSP-only scope.
Open a file with the .logo extension in IntelliJ IDEA after configuring LSP4IJ.
Example program:
to square :size
repeat 4 [
forward :size
right 90
]
end
square 100
With the language server connected:
- Keywords such as
to,repeat, andendare sent as semantic keyword tokens. - Built-ins such as
forwardandrightare classified as built-in procedures by the server. Depending on the IntelliJ theme, they may share a color with other token types or may not look visually distinct. squareis recognized as a user procedure declaration and call.:sizeresolves to the procedure parameter.- Go-to-declaration from
square 100navigates toto square :size. - Find references on
squarereturns semantic procedure references in the same file. Depending on the client's "include declaration" setting, the declaration line can appear in the references result as well as being the go-to-declaration target. - Completion suggests built-ins, user procedures, and visible variables based on cursor position.
Semantic tokens highlight LOGO keywords, built-ins, user procedures, parameters, and variables in the editor.
Go-to-declaration jumps from supported calls and variable references to their same-file declarations.
Find references returns semantic references for the selected procedure or variable binding.
Diagnostics appear in the editor and problems view after document open or change.
Completion suggests built-ins, user procedures, parameters, and visible variables based on cursor position.
The server targets a practical subset of the Turtle Academy LOGO dialect. Detailed language assumptions live in docs/turtle-logo-semantics.md.
Supported constructs include:
- Procedure declarations using
to ... endand supporteddefineforms. - Procedure calls for user-defined and built-in procedures.
- Parameters written as
:name. - Variable writes through
make,localmake, andname. - Variable reads through
:nameand supportedthing "nameforms. - Bracketed blocks and lists.
- Control structures such as
repeat,for,if,ifelse,dotimes,while, anduntil. - Basic expressions including numbers, words, identifiers, variable references, lists, parentheses, and supported binary operators.
Important semantic assumptions:
- The analysis unit is one document.
- Procedure declarations are file-level symbols.
- Parameters are procedure-local.
localmakedefines a local variable in the current scope.makeandnamefollow one consistent resolver rule for new bindings.:nameresolves to the nearest visible variable or parameter.- Built-ins are always available and are not user declarations.
- References are same-file only.
Feature work is driven by a cached DocumentAnalysis per document version (CachedAnalysisService: parse, resolve, diagnostics). LSP positions are mapped to lexer tokens via the DocumentAnalysis extensions in LogoFeatureTokenLookup (tokenAt, procedureNameSpan, tokenAtStartOffset).
What: Syntax highlighting for keywords, literals, built-in calls, user procedures, parameters, and variables via LSP semantic tokens rather than naive text rules.
How:
- Request:
textDocument/semanticTokens/fullinTortoiseTextDocumentService.semanticTokensFull. - Core:
LogoSemanticTokensServicewalks lexer tokens fromDocumentAnalysis.parseResultand joins resolution data (procedureReferences,variableReferences,variableDeclarations) to assignLogoSemanticTokenTypeper token. - Protocol:
SemanticTokensProtocolMapperproduces LSP delta arrays; the legend is advertised inServerCapabilityFactoryfromLogoSemanticTokenType. Token modifiers are always zero.
What: Jump from a call or variable use to the declaring procedure name or variable binding in the same file. Built-in procedures have no declaration target.
How:
- Request:
textDocument/definitioninTortoiseTextDocumentService.definition. - Core:
LogoDefinitionServiceusestokenAtandLogoTokenType:IDENTIFIERresolves viaprocedureReferencesto a non-built-in declaration and returns the procedure name span when possible (procedureNameSpan);VARIABLE_REFERENCEandWORD_LITERALresolve viavariableReferencestodeclaration.declarationSpan. - Protocol: returns a single
Locationfor the same document URI.
What: Find all uses of a user procedure or variable that share the same semantic binding; not plain-text search.
How:
- Request:
textDocument/referencesinTortoiseTextDocumentService.references, includingReferenceParams.context.includeDeclaration. - Core:
LogoReferenceServicekeeps references that share the same declaration object (procedureReferences/variableReferencesfiltered by declaration). Built-in call sites have no declaration, so find-references does not apply there. The procedure name in a header can resolve the declaration for the query. - Protocol: each hit is a
Locationwith the same URI.
What: Issues reported as LSP diagnostics in the editor and problems view after open or change.
How:
- Push:
didOpen,didChange, anddidCloseinTortoiseTextDocumentService;TortoiseLanguageServer.publishDiagnosticssendstextDocument/publishDiagnosticswithsourcetortoise-ls. - Core:
LogoDiagnosticsService.computeDiagnosticscovers unresolved procedures and variables, duplicate procedure declarations (procedureTable), and a filtered subset ofparseResult.errors(malformed procedure declaration messages only). - Protocol: ranges and messages come from
LogoDiagnostic.
What: Suggestions for built-ins, user procedures, and in-scope parameters and variables as : names, filtered by the typed prefix.
How:
- Request:
textDocument/completioninTortoiseTextDocumentService.completion. - Core:
LogoCompletionServiceandLogoCompletionEnginederive prefix and replace span, visible scopes, built-ins (excluding names shadowed by user procedures), user procedures, parameters, and variables with deterministic ordering (sortText,distinctBy). - Protocol:
toLspCompletionItemusesCompletionItem.textEditas aTextEditover the replace span.
The implementation is layered so that LSP protocol code stays separate from LOGO language semantics. The language core does not import LSP4J; protocol handlers convert between LSP4J types and internal, protocol-neutral models.
flowchart TD
editorClient["LSP Client"]
bootstrap["server.bootstrap"]
protocol["server.protocol"]
documents["application.documents"]
analysis["application.analysis"]
lexer["language.lexer"]
parser["language.parser"]
ast["language.ast"]
symbols["language.symbols"]
resolver["language.resolve"]
diagnostics["language.diagnostics"]
features["application.features"]
shared["shared.model / shared.text"]
editorClient --> bootstrap
bootstrap --> protocol
protocol --> documents
protocol --> analysis
documents --> analysis
analysis --> lexer
lexer --> parser
parser --> ast
ast --> symbols
symbols --> resolver
resolver --> diagnostics
analysis --> features
features --> shared
shared --> protocol
protocol --> editorClient
StdioServerBootstrapstartsTortoiseLanguageServerthrough LSP4J using standard input and output.TortoiseLanguageServeradvertises server capabilities: full text synchronization, semantic tokens, definitions, references, and completion.TortoiseTextDocumentServicereceives LSP events and requests.- On
didOpenanddidChange,InMemoryDocumentStorestores aDocumentSnapshot. The current synchronization strategy is full-text replacement throughFullTextSyncStrategy. CachedAnalysisServiceanalyzes the latest snapshot. It parses the text, resolves symbols and scopes, computes diagnostics, and caches the result by document URI and version.- Feature requests reuse the cached
DocumentAnalysisinstead of reparsing independently. - Protocol mappers convert internal spans, diagnostics, semantic tokens, and completion items back into LSP4J response types.
.
|-- README.md
|-- build.gradle.kts
|-- settings.gradle.kts
|-- docs/
| |-- implementation-plan.md
| |-- resources/
| `-- turtle-logo-semantics.md
|-- gradle/
|-- src/
| |-- main/
| | `-- kotlin/dev/tortoise/
| | |-- server/
| | |-- application/
| | |-- language/
| | `-- shared/
| `-- test/
| |-- kotlin/dev/tortoise/
| `-- resources/fixtures/
`-- gradlew / gradlew.bat
Key areas:
| Path | Responsibility |
|---|---|
server.bootstrap |
Starts the stdio LSP server process. |
server.protocol |
Owns LSP4J handlers, capabilities, lifecycle, and protocol mapping. |
application.documents |
Tracks open documents and applies full text synchronization. |
application.analysis |
Orchestrates parse, resolve, diagnostics, and versioned analysis caching. |
application.features |
Exposes editor-facing services for semantic tokens, definition, references, and completion. |
language.lexer |
Tokenizes Turtle Academy LOGO source text. |
language.parser |
Builds a tolerant parse result and AST from tokens. |
language.ast |
Defines the protocol-neutral LOGO syntax tree. |
language.symbols |
Collects procedure declarations and duplicate declarations. |
language.resolve |
Builds scopes, binds procedure and variable references, and identifies unresolved symbols. |
language.diagnostics |
Converts parse and resolution problems into internal diagnostics. |
language.completion |
Computes deterministic completion candidates from semantic state. |
shared.text |
Stores source positions and spans used across layers. |
shared.model |
Stores protocol-neutral diagnostics, semantic tokens, and completion models. |
docs/resources |
Images and GIFs used in this README to illustrate shipped features. |
src/test/resources/fixtures |
Golden fixtures for lexer, diagnostics, semantic tokens, and references. |
From the project root run:
-
Windows:
.\gradlew.bat test
-
macOS/Linux:
./gradlew test
The tests focus on the semantic core and LSP-facing behavior:
- Lexer tests and golden token fixtures check stable tokenization.
- Parser tests check supported syntax and malformed input recovery.
- Resolver tests check procedure, parameter, variable, loop, and built-in binding behavior.
- Diagnostics golden tests check user-facing error output.
- Semantic token golden tests check syntax-highlighting classification.
- Definition and reference tests check navigation based on semantic bindings.
- Completion tests check deterministic suggestions from built-ins, user procedures, and visible variables.
- A language server smoke test checks initialization and protocol-level behavior.
This is important because the editor features are not independent implementations. They all depend on the same parse and resolution result, so tests around the language core protect multiple LSP features at once.
The server intentionally excludes:
- Cross-file resolution.
- Workspace indexing.
- Rename support.
- Formatting support.
- Workspace symbols.
- Broad LOGO dialect coverage beyond the documented Turtle Academy subset.
- Extra editor features that are not part of the shipped feature set.
Known modeling boundaries:
- Analysis is single-document only.
- Semantic features depend on the latest opened or changed document version cached by the server.
- Built-in procedures come from a static catalog.
- The parser is tolerant and useful for editor feedback, but it is not a full Turtle Academy interpreter.
- The server does not execute LOGO programs.
This project is licensed under the MIT License. See LICENSE.





