├── lib-python
├── .python-version
├── setup.cfg
├── .gitignore
├── src
│ └── kson
│ │ └── _marker.c
├── setup.py
├── readme.md
├── uvw
├── uvw.bat
├── MANIFEST.in
└── pyproject.toml
├── src
├── jsMain
│ ├── kotlin
│ │ └── readme.md
│ └── resources
│ │ └── readme.md
├── jsTest
│ ├── kotlin
│ │ ├── readme.md
│ │ └── org
│ │ │ └── kson
│ │ │ └── testSupport
│ │ │ └── YamlValidator.kt
│ └── resources
│ │ └── readme.md
├── jvmMain
│ ├── kotlin
│ │ └── readme.md
│ └── resources
│ │ └── readme.md
├── jvmTest
│ ├── kotlin
│ │ ├── readme.md
│ │ └── org
│ │ │ └── kson
│ │ │ └── testSupport
│ │ │ └── YamlValidator.kt
│ └── resources
│ │ └── readme.md
├── nativeKsonTest
│ ├── readme.md
│ ├── resources
│ │ └── readme.md
│ └── kotlin
│ │ └── org
│ │ └── kson
│ │ └── testSupport
│ │ └── YamlValidator.kt
├── nativeMain
│ ├── kotlin
│ │ └── readme.md
│ └── resources
│ │ └── readme.md
├── commonMain
│ ├── resources
│ │ └── readme.md
│ └── kotlin
│ │ └── org
│ │ └── kson
│ │ ├── validation
│ │ └── Validator.kt
│ │ ├── stdlibx
│ │ ├── readme.md
│ │ ├── exceptions
│ │ │ ├── ShouldNotHappenException.kt
│ │ │ └── FatalParseException.kt
│ │ └── collections
│ │ │ └── Immutable.kt
│ │ ├── parser
│ │ ├── behavior
│ │ │ ├── readme.md
│ │ │ ├── StringUnquoted.kt
│ │ │ └── embedblock
│ │ │ │ ├── EmbedBlockSerialization.kt
│ │ │ │ └── EmbedBlockIndent.kt
│ │ └── MessageSink.kt
│ │ └── schema
│ │ └── validators
│ │ ├── AllOfValidator.kt
│ │ ├── MaxItemsValidator.kt
│ │ ├── MinItemsValidator.kt
│ │ ├── PropertyNamesValidator.kt
│ │ ├── MaximumValidator.kt
│ │ ├── MinimumValidator.kt
│ │ ├── MaxPropertiesValidator.kt
│ │ ├── MaxLengthValidator.kt
│ │ ├── MinLengthValidator.kt
│ │ ├── EnumValidator.kt
│ │ ├── PatternValidator.kt
│ │ ├── MinPropertiesValidator.kt
│ │ ├── ExclusiveMaximumValidator.kt
│ │ ├── ExclusiveMinimumValidator.kt
│ │ ├── NotValidator.kt
│ │ ├── OneOfValidator.kt
│ │ ├── ContainsValidator.kt
│ │ ├── IfValidator.kt
│ │ ├── RequiredValidator.kt
│ │ ├── MultipleOfValidator.kt
│ │ ├── UniqueItemsValidator.kt
│ │ ├── RefValidator.kt
│ │ ├── ConstValidator.kt
│ │ └── TypeValidator.kt
└── commonTest
│ ├── resources
│ └── readme.md
│ └── kotlin
│ └── org
│ └── kson
│ ├── testSupport
│ ├── YamlValidator.kt
│ └── JsonValidator.kt
│ ├── schema
│ ├── JsonSchemaTestNumber.kt
│ ├── JsonSchemaTestArray.kt
│ ├── JsonSchemaTestType.kt
│ └── validators
│ │ ├── ConstValidatorTest.kt
│ │ ├── AllOfValidatorTest.kt
│ │ └── AnyOfValidatorTest.kt
│ ├── KsonCoreTestNumberError.kt
│ ├── parser
│ ├── json
│ │ └── readme.md
│ ├── messages
│ │ └── MessagesTest.kt
│ └── ParserTest.kt
│ ├── KsonCoreTestBasicLiteral.kt
│ ├── KsonCoreTestEmbedBlockError.kt
│ ├── KsonCoreTestStringError.kt
│ └── KsonCoreTestNumber.kt
├── tooling
├── lsp-clients
│ ├── shared
│ │ ├── build.gradle.kts
│ │ ├── src
│ │ │ ├── connection
│ │ │ │ ├── index.ts
│ │ │ │ └── browserConnection.ts
│ │ │ ├── index.ts
│ │ │ └── config
│ │ │ │ └── index.ts
│ │ ├── extension
│ │ │ └── assets
│ │ │ │ └── kson-icon.png
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── monaco
│ │ ├── .gitignore
│ │ ├── src
│ │ │ ├── vite-env.d.ts
│ │ │ ├── worker
│ │ │ │ └── ksonServer.ts
│ │ │ ├── index.ts
│ │ │ ├── YamlEditorManager.ts
│ │ │ └── KsonEditorManager.ts
│ │ ├── readme.md
│ │ ├── playwright.config.ts
│ │ ├── resources
│ │ │ └── kson
│ │ │ │ └── example.kson
│ │ ├── package.json
│ │ ├── vite.config.ts
│ │ └── index.html
│ ├── vscode
│ │ ├── test
│ │ │ ├── workspace
│ │ │ │ └── .gitignore
│ │ │ ├── test-files.json
│ │ │ ├── browser
│ │ │ │ └── index.browser.ts
│ │ │ ├── node
│ │ │ │ └── index.node.ts
│ │ │ ├── mocha-config.ts
│ │ │ ├── suite
│ │ │ │ ├── assert.ts
│ │ │ │ ├── syntax-highlighting.test.ts
│ │ │ │ ├── common.ts
│ │ │ │ └── editing.test.ts
│ │ │ └── index.common.ts
│ │ ├── src
│ │ │ ├── server
│ │ │ │ ├── browser
│ │ │ │ │ └── ksonServerMain.ts
│ │ │ │ └── node
│ │ │ │ │ └── ksonServerMain.ts
│ │ │ ├── client
│ │ │ │ └── common
│ │ │ │ │ └── deactivate.ts
│ │ │ ├── stubs
│ │ │ │ └── minimatch.stub.ts
│ │ │ └── config
│ │ │ │ └── clientOptions.ts
│ │ ├── .gitignore
│ │ ├── tsconfig.json
│ │ ├── .vscode
│ │ │ ├── tasks.json
│ │ │ └── launch.json
│ │ └── readme.md
│ ├── gradle.properties
│ ├── pixi.toml
│ ├── tsconfig.json
│ ├── package.json
│ ├── readme.md
│ └── build.gradle.kts
├── jetbrains
│ ├── src
│ │ ├── test
│ │ │ ├── resources
│ │ │ │ └── testData
│ │ │ │ │ ├── parser
│ │ │ │ │ ├── BlankFile.kson
│ │ │ │ │ ├── Number.kson
│ │ │ │ │ ├── UnopenedList.kson
│ │ │ │ │ ├── NumberError.kson
│ │ │ │ │ ├── Object.kson
│ │ │ │ │ ├── UnclosedString.kson
│ │ │ │ │ ├── NumberDanglingExponentError.kson
│ │ │ │ │ ├── BlankFile.txt
│ │ │ │ │ ├── EmptyCommaError.kson
│ │ │ │ │ ├── UnclosedEmbedBlock.kson
│ │ │ │ │ ├── TwoConsecutiveStrings.kson
│ │ │ │ │ ├── DelimitedDashList.kson
│ │ │ │ │ ├── Number.txt
│ │ │ │ │ ├── NumberError.txt
│ │ │ │ │ ├── UnopenedList.txt
│ │ │ │ │ ├── UnclosedString.txt
│ │ │ │ │ ├── ManyConstructs.kson
│ │ │ │ │ ├── UnclosedEmbedBlock.txt
│ │ │ │ │ ├── Object.txt
│ │ │ │ │ ├── NumberDanglingExponentError.txt
│ │ │ │ │ ├── TwoConsecutiveStrings.txt
│ │ │ │ │ └── EmptyCommaError.txt
│ │ │ │ │ ├── rename
│ │ │ │ │ ├── foo.xml
│ │ │ │ │ └── foo_after.xml
│ │ │ │ │ ├── folding
│ │ │ │ │ ├── singleLineShouldNotFold.kson
│ │ │ │ │ ├── objectFolding.kson
│ │ │ │ │ ├── dashListFolding.kson
│ │ │ │ │ ├── bracketListFolding.kson
│ │ │ │ │ ├── angleListFolding.kson
│ │ │ │ │ └── embedBlockFolding.kson
│ │ │ │ │ └── annotator
│ │ │ │ │ ├── stringValues.kson
│ │ │ │ │ └── objectKeys.kson
│ │ │ └── kotlin
│ │ │ │ └── org
│ │ │ │ └── kson
│ │ │ │ └── jetbrains
│ │ │ │ ├── editor
│ │ │ │ ├── KsonCommenterTest.kt
│ │ │ │ ├── KsonQuoteMatcherTest.kt
│ │ │ │ └── KsonPairedBraceMatcherTest.kt
│ │ │ │ ├── highlighter
│ │ │ │ └── KsonSemanticHighlightAnnotatorTest.kt
│ │ │ │ ├── folding
│ │ │ │ └── KsonFoldingTest.kt
│ │ │ │ ├── psi
│ │ │ │ └── KsonElementGeneratorTest.kt
│ │ │ │ ├── parser
│ │ │ │ └── KsonLexerTest.kt
│ │ │ │ └── KsonPsiFileTest.kt
│ │ └── main
│ │ │ ├── resources
│ │ │ ├── icons
│ │ │ │ ├── kson_icon.png
│ │ │ │ └── kson_icon@2x.png
│ │ │ └── messages
│ │ │ │ └── KsonBundle.properties
│ │ │ └── kotlin
│ │ │ └── org
│ │ │ └── kson
│ │ │ └── jetbrains
│ │ │ ├── KsonLanguage.kt
│ │ │ ├── psi
│ │ │ ├── KsonPsiElement.kt
│ │ │ ├── KsonPsiFile.kt
│ │ │ └── KsonEmbedBlock.kt
│ │ │ ├── KsonIcons.kt
│ │ │ ├── config
│ │ │ └── KsonCodeStyleSettings.kt
│ │ │ ├── highlighter
│ │ │ ├── KsonSyntaxHighlighterFactory.kt
│ │ │ └── KsonSemanticHighlightAnnotator.kt
│ │ │ ├── editor
│ │ │ ├── KsonQuoteHandler.kt
│ │ │ ├── KsonCommenter.kt
│ │ │ ├── KsonCompletionContributor.kt
│ │ │ ├── KsonPairedBraceMatcher.kt
│ │ │ └── EmbedBlockLanguageListCompletionProvider.kt
│ │ │ ├── KsonBundle.kt
│ │ │ ├── file
│ │ │ └── KsonFileType.kt
│ │ │ ├── util
│ │ │ ├── DocumentExtensions.kt
│ │ │ └── PsiFileExtensions.kt
│ │ │ ├── formatter
│ │ │ └── KsonExternalFormatter.kt
│ │ │ ├── inject
│ │ │ ├── KsonLanguageInjectionContributor.kt
│ │ │ └── KsonLanguageInjectionPerformer.kt
│ │ │ ├── parser
│ │ │ └── KsonParserDefinition.kt
│ │ │ └── pages
│ │ │ └── KsonColorSettingsPage.kt
│ ├── gradle.properties
│ └── readme.md
├── language-server-protocol
│ ├── .mocharc.json
│ ├── examples
│ │ ├── .kson-schema.kson
│ │ ├── config
│ │ │ └── app.config.kson
│ │ └── schemas
│ │ │ └── config.schema.kson
│ ├── gradle.properties
│ ├── pixi.toml
│ ├── src
│ │ ├── core
│ │ │ ├── schema
│ │ │ │ ├── createSchemaProvider.browser.ts
│ │ │ │ ├── createSchemaProvider.node.ts
│ │ │ │ ├── SchemaProvider.ts
│ │ │ │ └── SchemaConfig.ts
│ │ │ ├── features
│ │ │ │ ├── FormattingService.ts
│ │ │ │ ├── HoverService.ts
│ │ │ │ └── CodeLensService.ts
│ │ │ ├── commands
│ │ │ │ ├── CommandExecutorFactory.ts
│ │ │ │ ├── createCommandExecutor.browser.ts
│ │ │ │ ├── createCommandExecutor.node.ts
│ │ │ │ ├── CommandType.ts
│ │ │ │ ├── CommandExecutor.browser.ts
│ │ │ │ ├── CommandParameters.ts
│ │ │ │ ├── CommandExecutor.node.ts
│ │ │ │ ├── AssociateSchemaCommand.ts
│ │ │ │ └── RemoveSchemaCommand.ts
│ │ │ └── KsonSettings.ts
│ │ ├── index.browser.ts
│ │ └── index.node.ts
│ ├── tsconfig.json
│ ├── build.gradle.kts
│ ├── README.md
│ └── package.json
├── cli
│ ├── src
│ │ └── main
│ │ │ └── kotlin
│ │ │ └── org
│ │ │ └── kson
│ │ │ └── tooling
│ │ │ └── cli
│ │ │ ├── generated
│ │ │ └── Version.kt
│ │ │ └── CommandLineInterface.kt
│ ├── gradle.properties
│ ├── pixi.toml
│ ├── readme.md
│ └── pixi.lock
└── readme.md
├── lib-rust
├── .gitignore
├── readme.md
├── .gitattributes
├── kson-sys
│ ├── src
│ │ └── lib.rs
│ └── Cargo.toml
├── kson
│ ├── Cargo.toml
│ ├── src
│ │ └── lib.rs
│ └── readme.md
├── build.gradle.kts
├── pixiw.bat
└── pixi.toml
├── assets
└── logo
│ ├── kson_logo_blue.png
│ ├── kson_logo_transparent.png
│ ├── kson_logo_blue_800x800.png
│ ├── kson_logo_transparent_800x800.png
│ ├── readme.md
│ └── generate.sh
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── kson-lib
├── pixi.toml
├── readme.md
├── pixiw.bat
└── pixi.lock
├── settings.gradle.kts
├── .gitignore
├── kson-tooling-lib
├── build.gradle.kts
└── readme.md
├── gradle.properties
└── jdk.properties
/lib-python/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/readme.md:
--------------------------------------------------------------------------------
1 | js-specific code
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib-rust/.gitignore:
--------------------------------------------------------------------------------
1 | artifacts
2 | kotlin
3 |
--------------------------------------------------------------------------------
/src/jsTest/kotlin/readme.md:
--------------------------------------------------------------------------------
1 | js-specific tests
--------------------------------------------------------------------------------
/src/jvmMain/kotlin/readme.md:
--------------------------------------------------------------------------------
1 | jvm-specific code
--------------------------------------------------------------------------------
/src/jvmTest/kotlin/readme.md:
--------------------------------------------------------------------------------
1 | jvm-specific tests
--------------------------------------------------------------------------------
/lib-rust/readme.md:
--------------------------------------------------------------------------------
1 | See readme at `kson/readme.md`
2 |
--------------------------------------------------------------------------------
/src/jsMain/resources/readme.md:
--------------------------------------------------------------------------------
1 | js-specific resources
--------------------------------------------------------------------------------
/src/jvmMain/resources/readme.md:
--------------------------------------------------------------------------------
1 | jvm-specific resources
--------------------------------------------------------------------------------
/src/nativeKsonTest/readme.md:
--------------------------------------------------------------------------------
1 | native-specific tests
--------------------------------------------------------------------------------
/src/nativeMain/kotlin/readme.md:
--------------------------------------------------------------------------------
1 | native-specific code
--------------------------------------------------------------------------------
/src/commonMain/resources/readme.md:
--------------------------------------------------------------------------------
1 | all-platform resources
--------------------------------------------------------------------------------
/src/jsTest/resources/readme.md:
--------------------------------------------------------------------------------
1 | js-specific test resources
--------------------------------------------------------------------------------
/src/jvmTest/resources/readme.md:
--------------------------------------------------------------------------------
1 | jvm-specific test resources
--------------------------------------------------------------------------------
/lib-python/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | py_limited_api = cp310
--------------------------------------------------------------------------------
/src/commonTest/resources/readme.md:
--------------------------------------------------------------------------------
1 | all-platform test resources
--------------------------------------------------------------------------------
/src/nativeMain/resources/readme.md:
--------------------------------------------------------------------------------
1 | native-specific resources
--------------------------------------------------------------------------------
/src/nativeKsonTest/resources/readme.md:
--------------------------------------------------------------------------------
1 | native-specific test resources
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/BlankFile.kson:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/Number.kson:
--------------------------------------------------------------------------------
1 | 42.42
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/UnopenedList.kson:
--------------------------------------------------------------------------------
1 | ]
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/NumberError.kson:
--------------------------------------------------------------------------------
1 | 42eope42
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/Object.kson:
--------------------------------------------------------------------------------
1 | key: value
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/UnclosedString.kson:
--------------------------------------------------------------------------------
1 | "unclosed
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/.gitignore:
--------------------------------------------------------------------------------
1 | .vite
2 | playwright-report
3 | test-results
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/src/connection/index.ts:
--------------------------------------------------------------------------------
1 | export * from './browserConnection';
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/NumberDanglingExponentError.kson:
--------------------------------------------------------------------------------
1 | key: 42e
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/BlankFile.txt:
--------------------------------------------------------------------------------
1 | FILE(0,0)
2 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './config';
2 | export * from './connection';
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/EmptyCommaError.kson:
--------------------------------------------------------------------------------
1 | [,,,, x ,, y ,,,,,,, z ,,,,]
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/workspace/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore our test .kson files
2 | *.kson
3 | .vscode
--------------------------------------------------------------------------------
/assets/logo/kson_logo_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/assets/logo/kson_logo_blue.png
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/rename/foo.xml:
--------------------------------------------------------------------------------
1 |
2 | 1>Foo
3 |
4 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/rename/foo_after.xml:
--------------------------------------------------------------------------------
1 |
2 | Foo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/UnclosedEmbedBlock.kson:
--------------------------------------------------------------------------------
1 | %
2 | This embed block is not closed...
--------------------------------------------------------------------------------
/assets/logo/kson_logo_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/assets/logo/kson_logo_transparent.png
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/TwoConsecutiveStrings.kson:
--------------------------------------------------------------------------------
1 | "a string"
2 | "an illegal second string"
--------------------------------------------------------------------------------
/assets/logo/kson_logo_blue_800x800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/assets/logo/kson_logo_blue_800x800.png
--------------------------------------------------------------------------------
/assets/logo/kson_logo_transparent_800x800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/assets/logo/kson_logo_transparent_800x800.png
--------------------------------------------------------------------------------
/lib-rust/.gitattributes:
--------------------------------------------------------------------------------
1 | # SCM syntax highlighting & preventing 3-way merges
2 | pixi.lock merge=binary linguist-language=YAML linguist-generated=true
3 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/folding/singleLineShouldNotFold.kson:
--------------------------------------------------------------------------------
1 | brace_list: ["item1", "item2"]
2 | dash_list: - 1
3 | object: {key: "value"}
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/src/worker/ksonServer.ts:
--------------------------------------------------------------------------------
1 | import { createAndStartBrowserWorker } from "@kson/lsp-shared";
2 |
3 | createAndStartBrowserWorker();
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/resources/icons/kson_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/tooling/jetbrains/src/main/resources/icons/kson_icon.png
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/extension/assets/kson-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/tooling/lsp-clients/shared/extension/assets/kson-icon.png
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/resources/icons/kson_icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kson-org/kson/HEAD/tooling/jetbrains/src/main/resources/icons/kson_icon@2x.png
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/src/server/browser/ksonServerMain.ts:
--------------------------------------------------------------------------------
1 | import { createAndStartBrowserWorker } from "@kson/lsp-shared";
2 |
3 | createAndStartBrowserWorker();
--------------------------------------------------------------------------------
/tooling/language-server-protocol/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension": ["ts"],
3 | "spec": "src/test/**/*.test.ts",
4 | "node-option": [
5 | "import=tsx"
6 | ]
7 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/folding/objectFolding.kson:
--------------------------------------------------------------------------------
1 | object: {
2 | key1: "value1",
3 | key2: "value2"
4 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/.gitignore:
--------------------------------------------------------------------------------
1 | # Directories to ignore
2 | # Generated for vscode test instance
3 | .vscode-test
4 | .vscode-test-web
5 | # Generated by the es-build
6 | dist
--------------------------------------------------------------------------------
/lib-python/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .pytest_cache
3 | .venv
4 | kson-sdist
5 | *.dll
6 | *.dylib
7 | *.so
8 | *.h
9 | *.egg-info
10 | *.pyd
11 |
12 | # uvw generated files
13 | .uv
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/DelimitedDashList.kson:
--------------------------------------------------------------------------------
1 | <
2 | - "one"
3 | - "two"
4 | - <
5 | - "sublist one"
6 | - "sublist two"
7 | >
8 | - "three"
9 | >
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/Number.txt:
--------------------------------------------------------------------------------
1 | FILE(0,5)
2 | KsonPsiElement(IElementTokenType(tokenType=NUMBER))(0,5)
3 | PsiElement(IElementTokenType(tokenType=NUMBER))('42.42')(0,5)
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "resolveJsonModule": true,
5 | "outDir": "./dist"
6 | },
7 | "include": ["src/**/*"]
8 | }
--------------------------------------------------------------------------------
/lib-rust/kson-sys/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_upper_case_globals)]
2 | #![allow(non_camel_case_types)]
3 | #![allow(non_snake_case)]
4 | #![allow(dead_code)]
5 |
6 | include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
7 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/NumberError.txt:
--------------------------------------------------------------------------------
1 | FILE(0,8)
2 | PsiErrorElement:Invalid character `o` found in this number(0,8)
3 | PsiElement(IElementTokenType(tokenType=NUMBER))('42eope42')(0,8)
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "resolveJsonModule": true
5 | },
6 | "include": ["src/**/*", "test/**/*", "test/test-files.json"]
7 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/testSupport/YamlValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.testSupport
2 |
3 | /**
4 | * Validate whether the given [yamlString] parses as legal Yaml
5 | */
6 | expect fun validateYaml(yamlString: String)
--------------------------------------------------------------------------------
/src/jsTest/kotlin/org/kson/testSupport/YamlValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.testSupport
2 |
3 | actual fun validateYaml(yamlString: String) {
4 | // No-op implementation for JS, we verify our test Yaml in the JVM impl
5 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/UnopenedList.txt:
--------------------------------------------------------------------------------
1 | FILE(0,1)
2 | PsiErrorElement:This must close a list, but this is not a list(0,1)
3 | PsiElement(IElementTokenType(tokenType=SQUARE_BRACKET_R))(']')(0,1)
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/readme.md:
--------------------------------------------------------------------------------
1 | # Monaco Editor
2 |
3 | Implementation of a monaco editor using the KSON language client. This LSP is used for the examples and playground on
4 | the [KSON website](https://kson.org).
5 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/KsonLanguage.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains
2 |
3 | import com.intellij.lang.Language
4 |
5 | object KsonLanguage : Language("kson") {
6 | const val NAME = "KSON"
7 | }
8 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/examples/.kson-schema.kson:
--------------------------------------------------------------------------------
1 | {
2 | "schemas": [
3 | {
4 | "fileMatch": ["config/*.kson", "**/*.config.kson"],
5 | "schema": "schemas/config.schema.json"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/src/nativeKsonTest/kotlin/org/kson/testSupport/YamlValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.testSupport
2 |
3 | actual fun validateYaml(yamlString: String) {
4 | // No-op implementation for Native, we verify our test Yaml in the JVM impl
5 | }
--------------------------------------------------------------------------------
/tooling/language-server-protocol/examples/config/app.config.kson:
--------------------------------------------------------------------------------
1 | appName: "MyApplication"
2 | version: "1.0.0"
3 | server: {
4 | host: "localhost"
5 | port: 8080
6 | enabled: true
7 | }
8 | features: [
9 | "logging"
10 | "metrics"
11 | ]
12 |
--------------------------------------------------------------------------------
/lib-python/src/kson/_marker.c:
--------------------------------------------------------------------------------
1 | // Minimal C extension to mark wheel as platform-specific
2 | #include
3 | static struct PyModuleDef m = {PyModuleDef_HEAD_INIT, "_marker", "", -1, NULL};
4 | PyMODINIT_FUNC PyInit__marker(void) {return PyModule_Create(&m);}
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/validation/Validator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.validation
2 |
3 | import org.kson.parser.MessageSink
4 | import org.kson.value.KsonValue
5 |
6 | interface Validator {
7 | fun validate(ksonValue: KsonValue, messageSink: MessageSink)
8 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/annotator/stringValues.kson:
--------------------------------------------------------------------------------
1 | {
2 | list: ["not a key", "also not a key"]
3 | value: "this is a value, not a key"
4 | }
--------------------------------------------------------------------------------
/tooling/cli/src/main/kotlin/org/kson/tooling/cli/generated/Version.kt:
--------------------------------------------------------------------------------
1 | // AUTO-GENERATED FILE - DO NOT EDIT
2 | // Generated by generateVersion task in build.gradle.kts
3 | package org.kson.tooling.cli.generated
4 |
5 | internal const val KSON_VERSION = "0.3.0-SNAPSHOT"
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/psi/KsonPsiElement.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.psi
2 |
3 | import com.intellij.extapi.psi.ASTWrapperPsiElement
4 | import com.intellij.lang.ASTNode
5 |
6 | open class KsonPsiElement(node: ASTNode) : ASTWrapperPsiElement(node)
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/folding/dashListFolding.kson:
--------------------------------------------------------------------------------
1 | simple_list: - "item1"
2 | - "item2"
3 |
4 | nested_list: - "outer1"
5 | - [
6 | "inner1",
7 | "inner2"
8 | ]
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/test-files.json:
--------------------------------------------------------------------------------
1 | {
2 | "tests": [
3 | "syntax-highlighting.test",
4 | "status-bar-schema-association.test",
5 | "schema-loading.test",
6 | "formatting-settings.test",
7 | "editing.test",
8 | "diagnostics.test"
9 | ]
10 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/KsonIcons.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains
2 |
3 | import com.intellij.openapi.util.IconLoader
4 |
5 | class KsonIcons {
6 | companion object {
7 | val FILE = IconLoader.getIcon("/icons/kson_icon.png", KsonIcons::class.java)
8 | }
9 | }
--------------------------------------------------------------------------------
/tooling/cli/gradle.properties:
--------------------------------------------------------------------------------
1 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
2 | org.gradle.configuration-cache = true
3 |
4 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
5 | org.gradle.caching = true
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/gradle.properties:
--------------------------------------------------------------------------------
1 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
2 | org.gradle.configuration-cache = true
3 |
4 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
5 | org.gradle.caching = true
6 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/src/index.ts:
--------------------------------------------------------------------------------
1 | export { createKsonEditor } from './KsonEditorManager.js';
2 | export { createYamlEditor } from './YamlEditorManager.js';
3 | // Re-export useful types from monaco-editor-wrapper
4 | export type { WrapperConfig, MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper';
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/stdlibx/readme.md:
--------------------------------------------------------------------------------
1 | # Stdlibx: Language/library-level extensions and utilities
2 |
3 | A collection of generally reusable [`stdlib`](https://kotlinlang.org/api/core/kotlin-stdlib/)-type code.
4 |
5 | > Proven elements of this package may graduate to a shared project/library at some point.
--------------------------------------------------------------------------------
/tooling/language-server-protocol/gradle.properties:
--------------------------------------------------------------------------------
1 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
2 | org.gradle.configuration-cache = true
3 |
4 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
5 | org.gradle.caching = true
6 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/folding/bracketListFolding.kson:
--------------------------------------------------------------------------------
1 | simple_list: [
2 | "item1",
3 | "item2",
4 | "item3"
5 | ]
6 |
7 | nested_list: [
8 | [
9 | "inner"
10 | ],
11 | "outer"
12 | ]
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/folding/angleListFolding.kson:
--------------------------------------------------------------------------------
1 | simple_list: <
2 | - "item1"
3 | - "item2"
4 | >
5 |
6 | nested_list: <
7 | - "item1"
8 | - <
9 | - "inner"
10 | >
11 | - "item3"
12 | >
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/UnclosedString.txt:
--------------------------------------------------------------------------------
1 | FILE(0,9)
2 | KsonPsiElement([Kson-parsed] QUOTED_STRING)(0,9)
3 | PsiErrorElement:Unclosed string(0,9)
4 | PsiElement(IElementTokenType(tokenType=STRING_OPEN_QUOTE))('"')(0,1)
5 | PsiElement(IElementTokenType(tokenType=STRING_CONTENT))('unclosed')(1,9)
6 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/ManyConstructs.kson:
--------------------------------------------------------------------------------
1 | {
2 | keyword_for_bool: false
3 | "keyword with spaces": "my string value"
4 | list_element: [1,3,3]
5 | my_embed: %sql
6 | this is some rad arbitrary embedded stuff, y'all
7 | %%
8 | sub_object: {
9 | thing: other
10 | }
11 | }
--------------------------------------------------------------------------------
/assets/logo/readme.md:
--------------------------------------------------------------------------------
1 | The base logo is stored in [kson_logo_transparent.svg](kson_logo_transparent.svg). After modifying, the [generate.sh](generate.sh) script can be used to regenerate the svg with the blue (`#90bcfe`) background as well as the 400x400 and 800x800 png equivalents.
2 |
3 | This bash script expects `inkscape` to be available on your path.
4 |
--------------------------------------------------------------------------------
/src/jvmTest/kotlin/org/kson/testSupport/YamlValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.testSupport
2 |
3 | import org.yaml.snakeyaml.Yaml
4 |
5 | actual fun validateYaml(yamlString: String) {
6 | try {
7 | Yaml().load(yamlString)
8 | } catch (e: Exception) {
9 | throw IllegalArgumentException("Invalid YAML: ${e.message}", e)
10 | }
11 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/parser/behavior/readme.md:
--------------------------------------------------------------------------------
1 | # Kson Behavior Documentation
2 |
3 | This package centralizes the "behaviors" for Kson Values, centralizing and owning the "truth" about how they should act.
4 | For instance, in [embedblock](org/kson/parser/behavior/embedblock) we store classes for EmbedBlock specific behaviors
5 | like how they handle indentating
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/stdlibx/exceptions/ShouldNotHappenException.kt:
--------------------------------------------------------------------------------
1 | package org.kson.stdlibx.exceptions
2 |
3 | /**
4 | * An exception for code paths that should never be hit
5 | *
6 | * @param whyNot the reason why we believe that this code path should never be hit
7 | */
8 | class ShouldNotHappenException(whyNot: String) : RuntimeException("This should not happen:\n$whyNot")
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/annotator/objectKeys.kson:
--------------------------------------------------------------------------------
1 | {
2 | simpleKey: "value"
3 | "quotedKey": "another value"
4 | nested: {
5 | innerKey: true
6 | }
7 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/browser/index.browser.ts:
--------------------------------------------------------------------------------
1 | // Browser test runner entry point
2 | import 'mocha/mocha';
3 | import { runTests } from '../index.common';
4 | import { browserConfig } from '../mocha-config';
5 |
6 | export function run(): Promise {
7 | // Configure mocha for browser
8 | mocha.setup(browserConfig);
9 |
10 | return runTests(mocha, true);
11 | }
--------------------------------------------------------------------------------
/kson-lib/pixi.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | authors = ["Adolfo Ochagavía "]
3 | channels = ["conda-forge"]
4 | name = "kson"
5 | platforms = [
6 | "linux-64",
7 | "linux-aarch64",
8 | "osx-arm64",
9 | "osx-64",
10 | "win-64",
11 | ]
12 | version = "0.1.0"
13 |
14 | [tasks]
15 |
16 | [target.win-64.dependencies]
17 | vs2022_win-64 = ">=19.44.35207,<20"
18 |
--------------------------------------------------------------------------------
/kson-lib/readme.md:
--------------------------------------------------------------------------------
1 | # The public interface for Kotlin
2 |
3 | This subproject defines [Kson.kt](src/commonMain/kotlin/org/kson/Kson.kt) the public Kotlin Multiplatform interface for Kson
4 |
5 | TODO:
6 | - confirm we're exposing exactly what we think we are, and add some validations to the build that prevent us from changing that by accident
7 | - define/document how we publish this artifact out
8 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/node/index.node.ts:
--------------------------------------------------------------------------------
1 | // Node.js test runner entry point
2 | import Mocha from 'mocha';
3 | import { runTests } from '../index.common';
4 | import { nodeConfig } from '../mocha-config';
5 |
6 | export function run(): Promise {
7 | // Create the mocha test instance for Node
8 | const mocha = new Mocha(nodeConfig);
9 |
10 | return runTests(mocha, false);
11 | }
--------------------------------------------------------------------------------
/lib-rust/kson-sys/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kson-sys"
3 | # [[kson-version-num]]
4 | version = "0.3.0-dev"
5 | edition = "2024"
6 | links = "kson"
7 | description = "Rust bindings to kson-lib"
8 | license = "Apache-2.0"
9 |
10 | [features]
11 | default = []
12 |
13 | [build-dependencies]
14 | anyhow = "1.0.100"
15 | bindgen = "0.71.0"
16 | flate2 = "1.1.2"
17 | tar = "0.4"
18 | ureq = "3.1.2"
19 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/pixi.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | authors = ["Adolfo Ochagavía "]
3 | channels = ["conda-forge"]
4 | name = "language-server-protocol"
5 | platforms = [
6 | "linux-64",
7 | "linux-aarch64",
8 | "osx-arm64",
9 | "osx-64",
10 | "win-64",
11 | ]
12 | version = "0.1.0"
13 |
14 | [tasks]
15 |
16 | [dependencies]
17 | nodejs = "20.19.4.*"
18 |
--------------------------------------------------------------------------------
/lib-rust/kson/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kson-rs"
3 | # [[kson-version-num]]
4 | version = "0.3.0-dev"
5 | edition = "2024"
6 | description = "Idiomatic Rust bindings to kson-lib"
7 | license = "Apache-2.0"
8 |
9 | [features]
10 | default = []
11 |
12 | [dependencies]
13 | # [[kson-version-num]]
14 | kson-sys = { version = "0.3.0-dev", path = "../kson-sys" }
15 |
16 | [dev-dependencies]
17 | insta = "1.43.1"
18 |
--------------------------------------------------------------------------------
/tooling/cli/pixi.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | authors = ["Adolfo Ochagavía "]
3 | channels = ["conda-forge"]
4 | name = "cli"
5 | platforms = [
6 | "linux-64",
7 | "linux-aarch64",
8 | "osx-arm64",
9 | "osx-64",
10 | "win-64",
11 | ]
12 | version = "0.1.0"
13 |
14 | [tasks]
15 |
16 | [dependencies]
17 |
18 | [target.win-64.dependencies]
19 | vs2022_win-64 = ">=19.44.35207,<20"
20 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/src/client/common/deactivate.ts:
--------------------------------------------------------------------------------
1 | import {BaseLanguageClient} from "vscode-languageclient";
2 |
3 | export let languageClient: BaseLanguageClient;
4 |
5 | /**
6 | * Deactivation function for language client.
7 | */
8 | export async function deactivate(): Promise {
9 | if (languageClient) {
10 | await languageClient.stop();
11 | languageClient = undefined;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/stdlibx/exceptions/FatalParseException.kt:
--------------------------------------------------------------------------------
1 | package org.kson.stdlibx.exceptions
2 |
3 | /**
4 | * An exception for code paths that are fatal if hit during parsing.
5 | *
6 | * These exceptions are caught and result in a null-AST
7 | *
8 | * @param message the reason why we believe that it is unexpected to hit this code path
9 | */
10 | class FatalParseException(message: String) : RuntimeException(message)
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/editor/KsonCommenterTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | class KsonCommenterTest : KsonEditorActionTest() {
4 | /**
5 | * Sanity check that [KsonCommenter] is correctly hooked up
6 | */
7 | fun testInsertLineComment() {
8 | doLineCommentTest(
9 | "key: value",
10 | "#key: value"
11 | )
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "compile",
6 | "type": "npm",
7 | "script": "compile-tests",
8 | "group": {
9 | "kind": "build"
10 | },
11 | "presentation": {
12 | "reveal": "silent"
13 | },
14 | "problemMatcher": ["$tsc"]
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/testSupport/JsonValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.testSupport
2 |
3 | import kotlinx.serialization.json.Json
4 |
5 | /**
6 | * Validate whether the given [jsonString] parses as legal JSON
7 | */
8 | fun validateJson(jsonString: String) {
9 | try {
10 | Json.parseToJsonElement(jsonString)
11 | } catch (e: Exception) {
12 | throw IllegalArgumentException("Invalid JSON: ${e.message}", e)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/folding/embedBlockFolding.kson:
--------------------------------------------------------------------------------
1 | dollar_block: $
2 | function test() {
3 | console.log("Hello");
4 | }
5 | $$
6 |
7 | percent_block: %
8 | function test() {
9 | console.log("Hello");
10 | }
11 | %%
12 |
13 | tagged_block: %kotlin
14 | function test() {
15 | console.log("Hello");
16 | }
17 | %%
--------------------------------------------------------------------------------
/lib-python/setup.py:
--------------------------------------------------------------------------------
1 | # Minimal setup.py - tells setuptools this package has compiled code
2 | from setuptools import setup, Extension
3 |
4 | setup(
5 | ext_modules=[
6 | Extension(
7 | "kson._marker",
8 | ["src/kson/_marker.c"],
9 | py_limited_api=True, # Use stable ABI for Python 3.x compatibility
10 | define_macros=[("Py_LIMITED_API", "0x030A0000")], # Python 3.10+ stable ABI
11 | )
12 | ]
13 | )
14 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/pixi.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | authors = ["Adolfo Ochagavía "]
3 | channels = ["conda-forge"]
4 | name = "language-server-protocol"
5 | platforms = [
6 | "linux-64",
7 | "linux-aarch64",
8 | "osx-arm64",
9 | "osx-64",
10 | "win-64",
11 | ]
12 | version = "0.1.0"
13 |
14 | [tasks]
15 |
16 | [dependencies]
17 | nodejs = "20.19.4.*"
18 | python = "3.13.*"
19 |
20 | [target.win-64.dependencies]
21 | vc14_runtime = "14.44.*"
22 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/schema/JsonSchemaTestNumber.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema
2 |
3 | import kotlin.test.Test
4 |
5 | class JsonSchemaTestNumber : JsonSchemaTest {
6 | @Test
7 | fun testNumberSchemaWithMinExclusive() {
8 |
9 | assertKsonEnforcesSchema(
10 | """
11 | 10
12 | """,
13 | """
14 | {"type": "number", "exclusiveMinimum": 20}
15 | """,
16 | false)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/readme.md:
--------------------------------------------------------------------------------
1 | # KSON VS Code Extension
2 |
3 | This extension provides language support for [KSON](https://kson.org) files in Visual Studio Code.
4 |
5 | ## Features
6 |
7 | - Syntax highlighting for `.kson` files
8 | - Language server integration
9 | - Auto-formatting
10 | - Diagnostics and error reporting
11 |
12 | For issues, feature requests, and source code, please visit the [plugin project home](https://github.com/kson-org/kson/tree/main/tooling/lsp-clients/vscode)
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/src/server/node/ksonServerMain.ts:
--------------------------------------------------------------------------------
1 | import { createConnection } from 'vscode-languageserver/node.js';
2 | import { startKsonServer } from 'kson-language-server/node';
3 |
4 | // Create connection for Node.js environment
5 | const connection = createConnection();
6 |
7 | // Set up console logging to use the connection
8 | console.log = connection.console.log.bind(connection.console);
9 | console.error = connection.console.error.bind(connection.console);
10 |
11 | startKsonServer(connection);
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/config/KsonCodeStyleSettings.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.config
2 |
3 | import com.intellij.psi.codeStyle.CodeStyleSettings
4 | import com.intellij.psi.codeStyle.CustomCodeStyleSettings
5 |
6 | class KsonCodeStyleSettings(codeStyleSettings: CodeStyleSettings)
7 | : CustomCodeStyleSettings("KsonCodeStyleSettings", codeStyleSettings) {
8 |
9 | companion object {
10 | const val INDENT_SIZE = 2
11 | const val TAB_SIZE = 2
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/UnclosedEmbedBlock.txt:
--------------------------------------------------------------------------------
1 | FILE(0,35)
2 | PsiErrorElement:Unclosed "%"(0,35)
3 | KsonPsiElement(IElementTokenType(tokenType=EMBED_OPEN_DELIM))(0,1)
4 | PsiElement(IElementTokenType(tokenType=EMBED_OPEN_DELIM))('%')(0,1)
5 | PsiElement(IElementTokenType(tokenType=EMBED_PREAMBLE_NEWLINE))('\n')(1,2)
6 | KsonEmbedContent(IElementTokenType(tokenType=EMBED_CONTENT))(2,35)
7 | PsiElement(IElementTokenType(tokenType=EMBED_CONTENT))('This embed block is not closed...')(2,35)
8 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/psi/KsonPsiFile.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.psi
2 |
3 | import com.intellij.extapi.psi.PsiFileBase
4 | import com.intellij.openapi.fileTypes.FileType
5 | import com.intellij.psi.FileViewProvider
6 | import org.kson.jetbrains.KsonLanguage
7 | import org.kson.jetbrains.file.KsonFileType
8 |
9 | class KsonPsiFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, KsonLanguage) {
10 | override fun getFileType(): FileType {
11 | return KsonFileType
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Run Extension",
6 | "type": "extensionHost",
7 | "request": "launch",
8 | "args": [
9 | "--disable-extensions",
10 | "--extensionDevelopmentPath=${workspaceFolder}"
11 | ],
12 | "outFiles": [
13 | "${workspaceFolder}/dist/**/*.js"
14 | ],
15 | "preLaunchTask": "compile",
16 | "sourceMaps": true,
17 | "trace": true,
18 | "env": {
19 | "KSON_DEBUG_PORT": "6009"
20 | }
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/mocha-config.ts:
--------------------------------------------------------------------------------
1 | // Centralized Mocha configuration
2 | export interface MochaConfig {
3 | ui: 'bdd' | 'tdd' | 'qunit' | 'exports';
4 | timeout: number;
5 | color?: boolean;
6 | reporter?: string;
7 | }
8 |
9 | export const commonConfig: MochaConfig = {
10 | ui: 'bdd',
11 | timeout: 10000,
12 | };
13 |
14 | export const nodeConfig: MochaConfig = {
15 | ...commonConfig,
16 | color: true,
17 | };
18 |
19 | export const browserConfig: MochaConfig = {
20 | ...commonConfig,
21 | reporter: undefined
22 | };
--------------------------------------------------------------------------------
/lib-rust/kson/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod generated;
2 | #[cfg(test)]
3 | mod test;
4 |
5 | pub use generated::*;
6 |
7 | fn kson_result_into_rust_result(r: Result) -> std::result::Result {
8 | match r {
9 | Result::Success(s) => Ok(s),
10 | Result::Failure(f) => Err(f),
11 | }
12 | }
13 |
14 | fn kson_schema_result_into_rust_result(r: SchemaResult) -> std::result::Result {
15 | match r {
16 | SchemaResult::Success(s) => Ok(s),
17 | SchemaResult::Failure(f) => Err(f),
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/highlighter/KsonSyntaxHighlighterFactory.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.highlighter
2 |
3 | import com.intellij.openapi.fileTypes.SyntaxHighlighter
4 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
5 | import com.intellij.openapi.project.Project
6 | import com.intellij.openapi.vfs.VirtualFile
7 |
8 |
9 | class KsonSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
10 | override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter {
11 | return KsonSyntaxHighlighter()
12 | }
13 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | plugins {
3 | kotlin("multiplatform") version "2.2.20"
4 | kotlin("jvm") version "2.2.20"
5 | kotlin("plugin.serialization") version "2.2.20"
6 | }
7 |
8 | repositories {
9 | gradlePluginPortal()
10 | mavenCentral()
11 | }
12 | }
13 |
14 | rootProject.name = "kson"
15 | include("kson-lib")
16 | include("kson-tooling-lib")
17 | include("lib-python")
18 | include("lib-rust")
19 | include("tooling:jetbrains")
20 | include("tooling:language-server-protocol")
21 | include("tooling:lsp-clients")
22 | include("tooling:cli")
23 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/Object.txt:
--------------------------------------------------------------------------------
1 | FILE(0,10)
2 | KsonPsiElement([Kson-parsed] OBJECT)(0,10)
3 | KsonPsiElement([Kson-parsed] OBJECT_PROPERTY)(0,10)
4 | KsonPsiElement([Kson-parsed] OBJECT_KEY)(0,3)
5 | KsonPsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))(0,3)
6 | PsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))('key')(0,3)
7 | PsiElement(IElementTokenType(tokenType=COLON))(':')(3,4)
8 | KsonPsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))(5,10)
9 | PsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))('value')(5,10)
10 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/AllOfValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonValue
4 | import org.kson.parser.MessageSink
5 | import org.kson.schema.JsonSchema
6 | import org.kson.schema.JsonSchemaValidator
7 |
8 | class AllOfValidator(val allOf: List) : JsonSchemaValidator {
9 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
10 | // log any and all error we see from this collection of schemas we must satisfy
11 | allOf.forEach {
12 | it.validate(ksonValue, messageSink)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MaxItemsValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonList
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonArrayValidator
7 |
8 | class MaxItemsValidator(private val maxItems: Long) : JsonArrayValidator() {
9 | override fun validateArray(node: KsonList, messageSink: MessageSink) {
10 | if (node.elements.size > maxItems) {
11 | messageSink.error(node.location, MessageType.SCHEMA_ARRAY_TOO_LONG.create(maxItems.toString()))
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MinItemsValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonList
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonArrayValidator
7 |
8 | class MinItemsValidator(private val minItems: Long) : JsonArrayValidator() {
9 | override fun validateArray(node: KsonList, messageSink: MessageSink) {
10 | if (node.elements.size < minItems) {
11 | messageSink.error(node.location, MessageType.SCHEMA_ARRAY_TOO_SHORT.create(minItems.toString()))
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/PropertyNamesValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonObject
4 | import org.kson.parser.MessageSink
5 | import org.kson.schema.JsonObjectValidator
6 | import org.kson.schema.JsonSchema
7 |
8 | class PropertyNamesValidator(private val propertyNamesSchema: JsonSchema?) : JsonObjectValidator() {
9 | override fun validateObject(node: KsonObject, messageSink: MessageSink) {
10 | node.propertyMap.forEach { (_, property) ->
11 | propertyNamesSchema?.validate(property.propName, messageSink)
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/KsonCoreTestNumberError.kt:
--------------------------------------------------------------------------------
1 | package org.kson
2 |
3 | import org.kson.parser.messages.MessageType.*
4 | import kotlin.test.Test
5 |
6 | class KsonCoreTestNumberError : KsonCoreTestError {
7 | @Test
8 | fun testDanglingExponentError() {
9 | assertParserRejectsSource(
10 | """
11 | 420E
12 | """,
13 | listOf(DANGLING_EXP_INDICATOR)
14 | )
15 |
16 | assertParserRejectsSource(
17 | """
18 | 420E-
19 | """,
20 | listOf(DANGLING_EXP_INDICATOR)
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/NumberDanglingExponentError.txt:
--------------------------------------------------------------------------------
1 | FILE(0,8)
2 | KsonPsiElement([Kson-parsed] OBJECT)(0,8)
3 | KsonPsiElement([Kson-parsed] OBJECT_PROPERTY)(0,8)
4 | KsonPsiElement([Kson-parsed] OBJECT_KEY)(0,3)
5 | KsonPsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))(0,3)
6 | PsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))('key')(0,3)
7 | PsiElement(IElementTokenType(tokenType=COLON))(':')(3,4)
8 | PsiErrorElement:Dangling exponent error: `e` must be followed by an exponent(5,8)
9 | PsiElement(IElementTokenType(tokenType=NUMBER))('42e')(5,8)
10 |
--------------------------------------------------------------------------------
/tooling/cli/readme.md:
--------------------------------------------------------------------------------
1 | # KSON CLI
2 |
3 | A command-line interface for working with KSON files.
4 |
5 | ## Building
6 |
7 | To build a native executable, run:
8 |
9 | ```bash
10 | ./gradlew :tooling:cli:buildNativeImage
11 | ```
12 |
13 | The native binary will be generated in `tooling/cli/build/native/nativeCompile/`.
14 |
15 | ## Usage
16 |
17 | The CLI provides several commands for working with KSON files:
18 |
19 | - `format` - Format KSON files
20 | - `analyze` - Analyze KSON files for issues
21 | - `json` or `yaml` - Convert KSON files to other formats
22 |
23 | Run the CLI with `--help` for more information on available commands and options.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Always-ignore extensions for common app-generated files
2 | *.diff
3 | *.err
4 | *.orig
5 | *.log
6 | *.rej
7 | *.swo
8 | *.swp
9 | *.vi
10 | *~
11 | *.sass-cache
12 | *.iml
13 | *.md.html
14 |
15 | # OS or Editor folders
16 | .DS_Store
17 | .cache
18 | .project
19 | .settings
20 | .tmproj
21 | nbproject
22 | Thumbs.db
23 |
24 | ## Directories to ignore
25 | # build directories
26 | out
27 | target
28 | build
29 | dist
30 | # gradlew generated files
31 | .gradle
32 | gradle/jdk
33 | # intellij settings
34 | .idea
35 | # kotlin metadata
36 | .kotlin
37 | # node generated files
38 | node_modules
39 | # pixi files
40 | .pixi
41 | pixiw
42 | pixiw.bat
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/schema/createSchemaProvider.browser.ts:
--------------------------------------------------------------------------------
1 | import {SchemaProvider} from './SchemaProvider.js';
2 | import {URI} from "vscode-uri";
3 |
4 | /**
5 | * Browser-specific schema provider factory.
6 | * Returns undefined since browsers don't have file system access.
7 | */
8 | export async function createSchemaProvider(
9 | workspaceRootUri: URI | undefined,
10 | logger: { info: (msg: string) => void; warn: (msg: string) => void; error: (msg: string) => void }
11 | ): Promise {
12 | logger.info('Running in browser environment, schema provider disabled');
13 | return undefined;
14 | }
--------------------------------------------------------------------------------
/tooling/readme.md:
--------------------------------------------------------------------------------
1 | # [Kson](https://kson.org) Tooling
2 |
3 | As a love letter to humans maintaining computer configuration, [Kson](https://kson.org) considers tooling integral. This folder contains the sources of official tools that are developed in tandem with the language to serve and delight all those lovely humans
4 |
5 | ### 🛠 💌 ➡ 👥
6 |
7 | ## Contents
8 |
9 | * [Kson support for JetBrains IDEs](jetbrains/readme.md)
10 | * [Implementation of the Language Server Protocol](./language-server-protocol)
11 | * [Monaco Editor](./lsp-clients/monaco)
12 | * [VS Code Plugin](./lsp-clients/vscode)
13 | * [Command Line Tool](./cli)
14 | * More to come...
15 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/features/FormattingService.ts:
--------------------------------------------------------------------------------
1 | import {TextEdit} from 'vscode-languageserver';
2 | import {KsonDocument} from '../document/KsonDocument.js';
3 | import {FormatOptions, Kson} from 'kson';
4 |
5 | /**
6 | * Service responsible for formatting Kson documents
7 | */
8 | export class FormattingService {
9 |
10 | formatDocument(document: KsonDocument, formatOptions: FormatOptions): TextEdit[] {
11 | const formattedKson = Kson.getInstance().format(document.getText(), formatOptions);
12 |
13 | return [
14 | TextEdit.replace(document.getFullDocumentRange(), formattedKson)
15 | ];
16 | }
17 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MaximumValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonNumber
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonNumberValidator
7 |
8 | class MaximumValidator(private val maximum: Double) : JsonNumberValidator() {
9 | override fun validateNumber(node: KsonNumber, messageSink: MessageSink) {
10 | val number = node.value.asDouble
11 | if (number > maximum) {
12 | messageSink.error(node.location, MessageType.SCHEMA_VALUE_TOO_LARGE.create(maximum.toString()))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MinimumValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonNumber
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonNumberValidator
7 |
8 | class MinimumValidator(private val minimum: Double) : JsonNumberValidator() {
9 | override fun validateNumber(node: KsonNumber, messageSink: MessageSink) {
10 | val number = node.value.asDouble
11 | if (number < minimum) {
12 | messageSink.error(node.location, MessageType.SCHEMA_VALUE_TOO_SMALL.create(minimum.toString()))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/CommandExecutorFactory.ts:
--------------------------------------------------------------------------------
1 | import {Connection} from "vscode-languageserver";
2 | import {FormattingService} from "../features/FormattingService";
3 | import {KsonDocumentsManager} from "../document/KsonDocumentsManager";
4 | import {KsonSettings} from "../KsonSettings";
5 | import {CommandExecutorBase} from "./CommandExecutor.base";
6 |
7 | export type CommandExecutorFactory = (
8 | connection: Connection,
9 | documentManager: KsonDocumentsManager,
10 | formattingService: FormattingService,
11 | getConfiguration: () => Required,
12 | workspaceRoot: string | null
13 | ) => CommandExecutorBase;
14 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MaxPropertiesValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonObject
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonObjectValidator
7 |
8 | class MaxPropertiesValidator(private val maxProperties: Long) : JsonObjectValidator() {
9 | override fun validateObject(node: KsonObject, messageSink: MessageSink) {
10 | if (node.propertyMap.size > maxProperties) {
11 | messageSink.error(node.location, MessageType.SCHEMA_OBJECT_TOO_MANY_PROPERTIES.create(maxProperties.toString()))
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/highlighter/KsonSemanticHighlightAnnotatorTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.highlighter
2 |
3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase
4 |
5 | class KsonSemanticHighlightAnnotatorTest : BasePlatformTestCase() {
6 |
7 | override fun getTestDataPath(): String {
8 | return "src/test/resources/testData/annotator"
9 | }
10 |
11 | fun testObjectKeys() {
12 | myFixture.testHighlighting(false, true, false, "objectKeys.kson")
13 | }
14 |
15 | fun testStringValues() {
16 | myFixture.testHighlighting(false, true, false, "stringValues.kson")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MaxLengthValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonString
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonStringValidator
7 |
8 | class MaxLengthValidator(private val maxLength: Long) : JsonStringValidator() {
9 | override fun validateString(node: KsonString, messageSink: MessageSink) {
10 | val str = node.value
11 | if (countCodePoints(str) > maxLength) {
12 | messageSink.error(node.location, MessageType.SCHEMA_STRING_LENGTH_TOO_LONG.create(maxLength.toString()))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/src/YamlEditorManager.ts:
--------------------------------------------------------------------------------
1 | import { MonacoEditorLanguageClientWrapper, WrapperConfig } from 'monaco-editor-wrapper';
2 | import { createYamlConfig } from './config/yamlConfig.js';
3 |
4 | /**
5 | * Creates a YAML editor with syntax highlighting support.
6 | * Returns the wrapper which can be managed using its native API.
7 | */
8 | export async function createYamlEditor(config: Partial = {}): Promise {
9 | const yamlConfig = createYamlConfig(config);
10 |
11 | const wrapper = new MonacoEditorLanguageClientWrapper();
12 | await wrapper.init(yamlConfig);
13 |
14 | return wrapper;
15 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MinLengthValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonString
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonStringValidator
7 |
8 | class MinLengthValidator(private val minLength: Long) : JsonStringValidator() {
9 | override fun validateString(node: KsonString, messageSink: MessageSink) {
10 | val str = node.value
11 | if (countCodePoints(str) < minLength) {
12 | messageSink.error(node.location, MessageType.SCHEMA_STRING_LENGTH_TOO_SHORT.create(minLength.toString()))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/EnumValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonList
4 | import org.kson.value.KsonValue
5 | import org.kson.parser.MessageSink
6 | import org.kson.parser.messages.MessageType
7 | import org.kson.schema.JsonSchemaValidator
8 |
9 | class EnumValidator(private val enum: KsonList) : JsonSchemaValidator {
10 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
11 | val enumValues = enum.elements
12 | if (!enumValues.contains(ksonValue)) {
13 | messageSink.error(ksonValue.location, MessageType.SCHEMA_ENUM_VALUE_NOT_ALLOWED.create())
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/index.browser.ts:
--------------------------------------------------------------------------------
1 | // Browser-specific exports
2 | import { Connection } from 'vscode-languageserver';
3 | import { startKsonServer as startKsonServerCore } from './startKsonServer.js';
4 | import { createSchemaProvider } from './core/schema/createSchemaProvider.browser.js';
5 | import { createCommandExecutor } from './core/commands/createCommandExecutor.browser.js';
6 |
7 | /**
8 | * Starts the Kson Language Server for browser environments.
9 | * No schema provider or file system-based command support.
10 | */
11 | export function startKsonServer(connection: Connection): void {
12 | startKsonServerCore(connection, createSchemaProvider, createCommandExecutor);
13 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/PatternValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonString
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonStringValidator
7 |
8 | class PatternValidator(pattern: String) : JsonStringValidator() {
9 | private val pattern = Regex(pattern)
10 | override fun validateString(node: KsonString, messageSink: MessageSink) {
11 | val str = node.value
12 | if (!pattern.containsMatchIn(str)) {
13 | messageSink.error(node.location, MessageType.SCHEMA_STRING_PATTERN_MISMATCH.create(pattern.pattern))
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/index.node.ts:
--------------------------------------------------------------------------------
1 | // Node.js-specific exports
2 | import { Connection } from 'vscode-languageserver';
3 | import { startKsonServer as startKsonServerCore } from './startKsonServer.js';
4 | import { createSchemaProvider } from './core/schema/createSchemaProvider.node.js';
5 | import { createCommandExecutor } from './core/commands/createCommandExecutor.node.js';
6 |
7 | /**
8 | * Starts the Kson Language Server for Node.js environments.
9 | * Includes file system-based schema provider and command executor support.
10 | */
11 | export function startKsonServer(connection: Connection): void {
12 | startKsonServerCore(connection, createSchemaProvider, createCommandExecutor);
13 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MinPropertiesValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonObject
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonObjectValidator
7 |
8 | class MinPropertiesValidator(private val minProperties: Long) : JsonObjectValidator() {
9 | override fun validateObject(node: KsonObject, messageSink: MessageSink) {
10 | if (node.propertyMap.size < minProperties) {
11 | messageSink.error(node.location,
12 | MessageType.SCHEMA_OBJECT_TOO_FEW_PROPERTIES.create(minProperties.toString())
13 | )
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/parser/json/readme.md:
--------------------------------------------------------------------------------
1 | # JSON Tests
2 |
3 | Kson is a superset of JSON. The tests in this directory validate that compliance
4 |
5 | The tests in [`generated/JsonSuiteTest`](generated/JsonSuiteTest.kt) are based on the [JSONTestSuite](https://github.com/nst/JSONTestSuite) project. They are generated by the `generateJsonTestSuite` task defined in the root [build.gradle.kts](../../../../../../../build.gradle.kts)
6 | > See [GenerateJsonTestSuiteTask](../../../../../../../buildSrc/src/main/kotlin/GenerateJsonTestSuiteTask.kt) and [JsonTestSuiteGenerator](../../../../../../../buildSrc/src/main/kotlin/org/kson/jsonsuite/JsonTestSuiteGenerator.kt) for more details on the `generateJsonTestSuite` task
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/ExclusiveMaximumValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonNumber
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonNumberValidator
7 |
8 | class ExclusiveMaximumValidator(private val exclusiveMaximum: Double) : JsonNumberValidator() {
9 | override fun validateNumber(node: KsonNumber, messageSink: MessageSink) {
10 | val number = node.value.asDouble
11 | if (number >= exclusiveMaximum) {
12 | messageSink.error(node.location, MessageType.SCHEMA_VALUE_TOO_LARGE_EXCLUSIVE.create(exclusiveMaximum.toString()))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/ExclusiveMinimumValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonNumber
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonNumberValidator
7 |
8 | class ExclusiveMinimumValidator(private val exclusiveMinimum: Double) : JsonNumberValidator() {
9 | override fun validateNumber(node: KsonNumber, messageSink: MessageSink) {
10 | val number = node.value.asDouble
11 | if (number <= exclusiveMinimum) {
12 | messageSink.error(node.location, MessageType.SCHEMA_VALUE_TOO_SMALL_EXCLUSIVE.create(exclusiveMinimum.toString()))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/schema/JsonSchemaTestArray.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema
2 |
3 | import kotlin.test.Test
4 |
5 | class JsonSchemaTestArray : JsonSchemaTest {
6 | @Test
7 | fun testEnforceConstraintsForMultipleTypes() {
8 |
9 | assertKsonEnforcesSchema(
10 | """
11 | [1, "string", true]
12 | """,
13 | """
14 | {"contains": { "type": "boolean" }}
15 | """,
16 | true)
17 |
18 | assertKsonEnforcesSchema(
19 | """
20 | [1, "string"]
21 | """,
22 | """
23 | {"contains": { "type": "boolean" }}
24 | """,
25 | false)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/editor/KsonQuoteHandler.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 | import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler
3 | import com.intellij.openapi.editor.highlighter.HighlighterIterator
4 | import org.kson.jetbrains.parser.elem
5 | import org.kson.parser.TokenType
6 |
7 |
8 | class KsonQuoteHandler : SimpleTokenSetQuoteHandler(elem(TokenType.STRING_OPEN_QUOTE), elem(TokenType.STRING_CLOSE_QUOTE)) {
9 |
10 | override fun isOpeningQuote(iterator: HighlighterIterator?, offset: Int): Boolean {
11 | if (iterator == null) {
12 | return false
13 | }
14 |
15 | return iterator.tokenType == elem(TokenType.STRING_OPEN_QUOTE)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "esnext",
5 | "moduleResolution": "bundler",
6 | "outDir": "out",
7 | // Custom types
8 | "typeRoots": [
9 | "./node_modules/@types"
10 | ],
11 | // Useful settings for debugging
12 | "declaration": true,
13 | "declarationMap": true,
14 | "sourceMap": true,
15 | "inlineSourceMap": false,
16 | "inlineSources": true,
17 | // CommonJS interop
18 | "allowSyntheticDefaultImports": true,
19 | "esModuleInterop": true,
20 | // Prevents cross-platform issues
21 | "forceConsistentCasingInFileNames": true,
22 | // Performance improvement
23 | "skipLibCheck": true
24 | }
25 | }
--------------------------------------------------------------------------------
/lib-rust/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins{
2 | base
3 | }
4 |
5 | val nativeKsonDir = project.projectDir.parentFile.resolve("kson-lib/build/kotlin/compileGraalVmNativeImage")
6 | val ksonRsTargetDir = project.projectDir.resolve("kson/target/debug")
7 |
8 | val testDynamic by tasks.registering(PixiExecTask::class) {
9 | dependsOn(":kson-lib:buildWithGraalVmNativeImage")
10 |
11 | group="verification"
12 | command=listOf("cargo", "test", "--manifest-path", "kson/Cargo.toml")
13 | envVars=mapOf(
14 | Pair("KSON_PREBUILT_BIN_DIR", nativeKsonDir.absolutePath),
15 | Pair("KSON_COPY_SHARED_LIBRARY_TO_DIR", ksonRsTargetDir.absolutePath),
16 | )
17 | }
18 |
19 | tasks{
20 | check {
21 | dependsOn(testDynamic)
22 | }
23 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/KsonBundle.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains
2 |
3 | import com.intellij.DynamicBundle
4 | import org.jetbrains.annotations.NonNls
5 | import org.jetbrains.annotations.PropertyKey
6 |
7 | @NonNls
8 | private const val BUNDLE = "messages.KsonBundle"
9 |
10 | object KsonBundle : DynamicBundle(BUNDLE) {
11 |
12 | @Suppress("SpreadOperator")
13 | @JvmStatic
14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) =
15 | getMessage(key, *params)
16 |
17 | @Suppress("SpreadOperator", "unused")
18 | @JvmStatic
19 | fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) =
20 | getLazyMessage(key, *params)
21 | }
22 |
--------------------------------------------------------------------------------
/kson-lib/pixiw.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | rem Simple Pixi wrapper for Windows that auto-installs Pixi locally if needed
4 |
5 | set PIXI_DIR=%cd%\..\.pixi
6 | set PIXI_BIN=%PIXI_DIR%\bin\pixi.exe
7 |
8 | rem Install Pixi locally if not present
9 | if not exist "%PIXI_BIN%" (
10 | echo Installing Pixi locally to %PIXI_DIR%...
11 |
12 | rem Use Pixi's official installation script with custom install location
13 | set PIXI_HOME=%PIXI_DIR%
14 | set PIXI_NO_PATH_UPDATE=1
15 |
16 | powershell -ExecutionPolicy ByPass -Command "iwr -useb https://pixi.sh/install.ps1 | iex"
17 |
18 | if not exist "%PIXI_BIN%" (
19 | echo Failed to install Pixi
20 | exit /b 1
21 | )
22 | )
23 |
24 | rem Execute Pixi with all arguments
25 | "%PIXI_BIN%" %*
--------------------------------------------------------------------------------
/lib-rust/pixiw.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | rem Simple Pixi wrapper for Windows that auto-installs Pixi locally if needed
4 |
5 | set PIXI_DIR=%cd%\..\.pixi
6 | set PIXI_BIN=%PIXI_DIR%\bin\pixi.exe
7 |
8 | rem Install Pixi locally if not present
9 | if not exist "%PIXI_BIN%" (
10 | echo Installing Pixi locally to %PIXI_DIR%...
11 |
12 | rem Use Pixi's official installation script with custom install location
13 | set PIXI_HOME=%PIXI_DIR%
14 | set PIXI_NO_PATH_UPDATE=1
15 |
16 | powershell -ExecutionPolicy ByPass -Command "iwr -useb https://pixi.sh/install.ps1 | iex"
17 |
18 | if not exist "%PIXI_BIN%" (
19 | echo Failed to install Pixi
20 | exit /b 1
21 | )
22 | )
23 |
24 | rem Execute Pixi with all arguments
25 | "%PIXI_BIN%" %*
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from '@playwright/test';
2 |
3 | export default defineConfig({
4 | testDir: './tests',
5 | timeout: 30 * 1000,
6 | expect: {
7 | timeout: 5000
8 | },
9 | fullyParallel: true,
10 | forbidOnly: !!process.env.CI,
11 | retries: process.env.CI ? 2 : 0,
12 | workers: process.env.CI ? 1 : undefined,
13 | reporter: 'html',
14 | use: {
15 | baseURL: 'http://localhost:5173',
16 | trace: 'on-first-retry',
17 | },
18 |
19 | projects: [
20 | {
21 | name: 'chromium',
22 | use: { ...devices['Desktop Chrome'] },
23 | },
24 | ],
25 |
26 | webServer: {
27 | command: 'npm run dev',
28 | port: 5173,
29 | reuseExistingServer: !process.env.CI,
30 | },
31 | });
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/NotValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonValue
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonSchema
7 | import org.kson.schema.JsonSchemaValidator
8 |
9 | class NotValidator(private val notSchema: JsonSchema?) : JsonSchemaValidator {
10 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
11 | if (notSchema == null) {
12 | return
13 | }
14 | val notMessageSink = MessageSink()
15 | if (notSchema.isValid(ksonValue, notMessageSink)) {
16 | messageSink.error(ksonValue.location, MessageType.SCHEMA_NOT_VALIDATION_FAILED.create())
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "outDir": "out",
7 | // Custom types
8 | "typeRoots": [
9 | "./node_modules/@types"
10 | ],
11 | // Useful settings for debugging
12 | "declaration": true,
13 | "declarationMap": true,
14 | "sourceMap": true,
15 | "inlineSourceMap": false,
16 | "inlineSources": true,
17 | // CommonJS interop
18 | "allowSyntheticDefaultImports": true,
19 | "esModuleInterop": true,
20 | // Prevents cross-platform issues
21 | "forceConsistentCasingInFileNames": true,
22 | // Performance improvement
23 | "skipLibCheck": true
24 | },
25 | "include": [
26 | "src"
27 | ]
28 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/file/KsonFileType.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.file
2 |
3 | import com.intellij.openapi.fileTypes.LanguageFileType
4 | import org.kson.jetbrains.KsonBundle
5 | import org.kson.jetbrains.KsonIcons
6 | import org.kson.jetbrains.KsonLanguage
7 | import javax.swing.Icon
8 |
9 | object KsonFileType : LanguageFileType(KsonLanguage) {
10 |
11 | override fun getName(): String {
12 | return KsonLanguage.NAME
13 | }
14 |
15 | override fun getDescription(): String {
16 | return KsonBundle.message("kson.file.fileTypeDescription")
17 | }
18 |
19 | override fun getDefaultExtension(): String {
20 | return "kson"
21 | }
22 |
23 | override fun getIcon(): Icon {
24 | return KsonIcons.FILE
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/TwoConsecutiveStrings.txt:
--------------------------------------------------------------------------------
1 | FILE(0,37)
2 | KsonPsiElement([Kson-parsed] QUOTED_STRING)(0,10)
3 | PsiElement(IElementTokenType(tokenType=STRING_OPEN_QUOTE))('"')(0,1)
4 | PsiElement(IElementTokenType(tokenType=STRING_CONTENT))('a string')(1,9)
5 | PsiElement(IElementTokenType(tokenType=STRING_CLOSE_QUOTE))('"')(9,10)
6 | PsiErrorElement:Unexpected trailing content. The previous content parsed as a complete Kson document.(11,37)
7 | KsonPsiElement([Kson-parsed] QUOTED_STRING)(11,37)
8 | PsiElement(IElementTokenType(tokenType=STRING_OPEN_QUOTE))('"')(11,12)
9 | PsiElement(IElementTokenType(tokenType=STRING_CONTENT))('an illegal second string')(12,36)
10 | PsiElement(IElementTokenType(tokenType=STRING_CLOSE_QUOTE))('"')(36,37)
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import tmLanguageJson from '../../extension/config/kson.tmLanguage.json';
2 | import languageConfigJson from '../../extension/config/language-configuration.json';
3 |
4 | export const tmLanguage = tmLanguageJson;
5 | export const languageConfiguration = languageConfigJson;
6 |
7 | // Export as strings for different consumption methods
8 | export const tmLanguageString = JSON.stringify(tmLanguageJson, null, 2);
9 | export const languageConfigurationString = JSON.stringify(languageConfigJson, null, 2);
10 |
11 | // Export individual config properties for type safety
12 | export const KSON_LANGUAGE_ID = 'kson';
13 | export const KSON_EXTENSIONS = ['.kson'];
14 | export const KSON_ALIASES = ['Kson', 'kson', 'KSON'];
15 | export const KSON_SCOPE_NAME = 'source.kson';
--------------------------------------------------------------------------------
/lib-python/readme.md:
--------------------------------------------------------------------------------
1 | # Python bindings for Kson's public API
2 |
3 | [KSON](https://kson.org) is available on PyPI for Linux, macOS and Windows.
4 |
5 | ## Installation
6 |
7 | Install from PyPI:
8 |
9 | ```bash
10 | pip install kson-lang
11 | ```
12 |
13 | ### Build from source
14 |
15 | ```bash
16 | git clone https://github.com/kson-org/kson.git
17 | cd kson && ./gradlew :lib-python:build
18 | pip install ./lib-python
19 | ```
20 |
21 | ## Example usage
22 |
23 |
24 | ```python
25 | from kson import Kson, Success
26 | result = Kson.to_json("key: [1, 2, 3, 4]")
27 | assert isinstance(result, Success)
28 | print(result.output())
29 | ```
30 |
31 | This should print the following to stdout:
32 |
33 | ```json
34 | {
35 | "key": [
36 | 1,
37 | 2,
38 | 3,
39 | 4
40 | ]
41 | }
42 | ```
43 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/resources/messages/KsonBundle.properties:
--------------------------------------------------------------------------------
1 | kson.name=Kson
2 | kson.file.fileTypeDescription=Kson language file
3 |
4 | kson.syntaxHighlighter.curly_brace=Curly Brace
5 | kson.syntaxHighlighter.square_bracket=Square Bracket
6 | kson.syntaxHighlighter.angle_bracket=Angle Bracket
7 | kson.syntaxHighlighter.colon=Colon
8 | kson.syntaxHighlighter.comma=Comma
9 | kson.syntaxHighlighter.comment=Comment
10 | kson.syntaxHighlighter.end-dot=End-dot
11 | kson.syntaxHighlighter.delimiter=Delimiter
12 | kson.syntaxHighlighter.embed_tag=Embed tag
13 | kson.syntaxHighlighter.unquoted=Unquoted String
14 | kson.syntaxHighlighter.keyword=Keyword
15 | kson.syntaxHighlighter.invalid=Invalid
16 | kson.syntaxHighlighter.number=Number
17 | kson.syntaxHighlighter.content=Content
18 | kson.syntaxHighlighter.key=Key
19 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/schema/JsonSchemaTestType.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema
2 |
3 | import kotlin.test.Test
4 |
5 | class JsonSchemaTestType : JsonSchemaTest {
6 | @Test
7 | fun testEnforceConstraintsForMultipleTypes() {
8 |
9 | assertKsonEnforcesSchema(
10 | """
11 | "this string is longer than 10 characters"
12 | """,
13 | """
14 | {"type": ["string", "number"], "exclusiveMinimum": 20, "maxLength": 10 }
15 | """,
16 | false)
17 |
18 | assertKsonEnforcesSchema(
19 | """
20 | 10
21 | """,
22 | """
23 | {"type": ["string", "number"], "exclusiveMinimum": 20, "maxLength": 10 }
24 | """,
25 | false)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/editor/KsonCommenter.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | import com.intellij.lang.Commenter
4 |
5 | /**
6 | * Configure a [Commenter] for Kson. Note this returns mostly nulls since Kson does not have
7 | * a Block Comment construct
8 | */
9 | internal class KsonCommenter : Commenter {
10 | override fun getLineCommentPrefix(): String {
11 | return "#"
12 | }
13 |
14 | override fun getBlockCommentPrefix(): String? {
15 | return null
16 | }
17 |
18 | override fun getBlockCommentSuffix(): String? {
19 | return null
20 | }
21 |
22 | override fun getCommentedBlockCommentPrefix(): String? {
23 | return null
24 | }
25 |
26 | override fun getCommentedBlockCommentSuffix(): String? {
27 | return null
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/KsonCoreTestBasicLiteral.kt:
--------------------------------------------------------------------------------
1 | package org.kson
2 |
3 | import kotlin.test.Test
4 |
5 | class KsonCoreTestBasicLiteral : KsonCoreTest {
6 | @Test
7 | fun testBooleanLiteralSource() {
8 | assertParsesTo(
9 | """
10 | true
11 | """,
12 | "true",
13 | "true",
14 | "true"
15 | )
16 |
17 | assertParsesTo(
18 | """
19 | false
20 | """,
21 | "false",
22 | "false",
23 | "false"
24 | )
25 | }
26 |
27 | @Test
28 | fun testNullLiteralSource() {
29 | assertParsesTo(
30 | """
31 | null
32 | """,
33 | "null",
34 | "null",
35 | "null"
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/kson-tooling-lib/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | }
4 |
5 | repositories {
6 | mavenCentral()
7 | }
8 |
9 | group = "org.kson"
10 | version = "0.1.2-SNAPSHOT"
11 | kotlin {
12 | jvm {
13 | testRuns["test"].executionTask.configure {
14 | useJUnit()
15 | }
16 | }
17 | js(IR) {
18 | browser()
19 | nodejs()
20 | binaries.library()
21 | useEsModules()
22 | generateTypeScriptDefinitions()
23 | }
24 |
25 | sourceSets {
26 | val commonMain by getting {
27 | dependencies {
28 | implementation(project(":"))
29 | }
30 | }
31 | val commonTest by getting {
32 | dependencies {
33 | implementation(kotlin("test"))
34 | }
35 | }
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/lib-rust/pixi.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | authors = ["Adolfo Ochagavía "]
3 | channels = ["conda-forge"]
4 | name = "lib-rust"
5 | platforms = [
6 | "linux-64",
7 | "linux-aarch64",
8 | "osx-arm64",
9 | "osx-64",
10 | "win-64",
11 | ]
12 | # [[kson-version-num]]
13 | version = "0.3.0-dev"
14 |
15 | [tasks]
16 |
17 | [dependencies]
18 | rust = "1.89.0.*"
19 |
20 | [target.win-64.dependencies]
21 | vs2022_win-64 = ">=19.44.35207,<20"
22 |
23 | [target.linux-64.dependencies]
24 | libclang = "20.*"
25 | sysroot_linux-64 = ">=2.34"
26 | zlib = "==1.3.1"
27 |
28 | [target.linux-64.activation.env]
29 | LIBCLANG_PATH = "$CONDA_PREFIX/lib"
30 |
31 | [target.linux-aarch64.dependencies]
32 | libclang = "20.*"
33 | sysroot_linux-64 = ">=2.34"
34 | zlib = "==1.3.1"
35 |
36 | [target.linux-aarch64.activation.env]
37 | LIBCLANG_PATH = "$CONDA_PREFIX/lib"
38 |
--------------------------------------------------------------------------------
/kson-tooling-lib/readme.md:
--------------------------------------------------------------------------------
1 | # Tooling API for KSON
2 |
3 | This subproject defines [KsonTooling.kt](src/commonMain/kotlin/org/kson/KsonTooling.kt), the public Kotlin Multiplatform interface for building tooling support for KSON.
4 |
5 | ## Available Tools
6 |
7 | The tooling API provides schema-aware information extraction at any location in a KSON document:
8 |
9 | - **Schema Information Retrieval** - Extract schema documentation, type information, constraints, and metadata for any position in a document
10 | - **Completion Suggestions** - Get valid property names, enum values, and type-appropriate suggestions based on the schema
11 | - **Document Path Navigation** - Navigate through KSON documents using schema-aware path building
12 |
13 | These capabilities can be used to build IDEs, linters, documentation generators, CLI tools, web editors, or any other tooling that needs schema-aware document analysis.
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/src/stubs/minimatch.stub.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Stub for minimatch module in browser builds.
3 | *
4 | * The vscode-languageclient library imports minimatch for features like:
5 | * - DiagnosticFeature (diagnostic pull mode)
6 | * - NotebookDocumentSyncFeature
7 | * - FileOperationFeature (create/rename/delete file operations)
8 | *
9 | * Since we don't use these features in the browser client, we provide
10 | * a minimal stub to prevent the full minimatch library from being bundled.
11 | */
12 |
13 | export class Minimatch {
14 | constructor(pattern: string, options?: any) {
15 | console.warn('minimatch stub called - this feature is not supported in browser mode');
16 | }
17 |
18 | match(path: string): boolean {
19 | return false;
20 | }
21 | }
22 |
23 | // Default export for different import styles
24 | export default {
25 | Minimatch
26 | };
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/OneOfValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonValue
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonSchema
7 | import org.kson.schema.JsonSchemaValidator
8 |
9 | class OneOfValidator(private val oneOf: List) : JsonSchemaValidator {
10 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
11 | val hasExactlyOneOf = oneOf.count {
12 | val oneOfMessageSink = MessageSink()
13 | it.isValid(ksonValue, oneOfMessageSink)
14 | } == 1
15 | if (!hasExactlyOneOf) {
16 | // schema todo maybe merge in some of the errors we found?
17 | messageSink.error(ksonValue.location, MessageType.SCHEMA_ONE_OF_VALIDATION_FAILED.create())
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | base
3 | }
4 |
5 | tasks {
6 | val npmInstall = register("npmInstall") {
7 | command=listOf("npm", "install")
8 | doNotTrackState("npm already tracks its own state")
9 | }
10 |
11 | register("npm_run_compile") {
12 | command=listOf("npm", "run", "compile")
13 | dependsOn(":kson-lib:jsNodeProductionLibraryDistribution")
14 | dependsOn(":kson-tooling-lib:jsNodeProductionLibraryDistribution")
15 | dependsOn(npmInstall)
16 | }
17 |
18 | register("npm_run_test") {
19 | command=listOf("npm", "run", "test")
20 | dependsOn("npm_run_compile")
21 | }
22 |
23 | check {
24 | dependsOn("npm_run_test")
25 | }
26 |
27 | clean {
28 | delete("out")
29 | delete("node_modules")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib-python/uvw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Simple uv wrapper that auto-installs uv locally if needed
4 |
5 | UV_DIR="$(pwd)/.uv"
6 | UV_BIN="$UV_DIR/uv"
7 |
8 | # Install uv locally if not present
9 | if [ ! -f "$UV_BIN" ]; then
10 | echo "Installing uv locally to $UV_DIR..."
11 |
12 | # Use uv's official installation script with custom install location
13 | export UV_UNMANAGED_INSTALL="$UV_DIR"
14 |
15 | if command -v curl >/dev/null 2>&1; then
16 | curl -fsSL https://astral.sh/uv/install.sh | bash
17 | elif command -v wget >/dev/null 2>&1; then
18 | wget -qO- https://astral.sh/uv/install.sh | bash
19 | else
20 | echo "Please install curl or wget" >&2
21 | exit 1
22 | fi
23 |
24 | if [ ! -f "$UV_BIN" ]; then
25 | echo "Failed to install uv" >&2
26 | exit 1
27 | fi
28 | fi
29 |
30 | # Execute uv with all arguments
31 | exec "$UV_BIN" "$@"
32 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/ContainsValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonList
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonArrayValidator
7 | import org.kson.schema.JsonSchema
8 |
9 | class ContainsValidator(private val containsSchema: JsonSchema) : JsonArrayValidator() {
10 | override fun validateArray(node: KsonList, messageSink: MessageSink) {
11 | val foundMatchingElement = node.elements.any { element ->
12 | val containsMessageSink = MessageSink()
13 | containsSchema.validate(element, containsMessageSink)
14 | !containsMessageSink.hasMessages()
15 | }
16 | if (!foundMatchingElement) {
17 | messageSink.error(node.location, MessageType.SCHEMA_CONTAINS_VALIDATION_FAILED.create())
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | kotlin.js.generate.executable.default=false
3 | org.gradle.jvmargs=-Xmx2048m
4 |
5 | # Explicitly opt-in to the Kotlin stlib that Gradle automatically provides
6 | # to quiet the warning around this from our :tooling:jetbrains subproject.
7 | # See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
8 | # for details.
9 | #
10 | # NOTE: it should be possible to set this in the :tooling:jetbrains subproject, closer to
11 | # the code that demands it, but there is currently a bug in how the warning detects this
12 | # setting (https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1789)
13 | kotlin.stdlib.default.dependency = true
14 |
15 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
16 | # disable the DokkaV2 welcome message in the build
17 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
18 |
19 | org.gradle.console=verbose
20 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/schema/createSchemaProvider.node.ts:
--------------------------------------------------------------------------------
1 | import {SchemaProvider} from './SchemaProvider.js';
2 | import {FileSystemSchemaProvider} from './FileSystemSchemaProvider';
3 | import {URI} from "vscode-uri";
4 |
5 | /**
6 | * Node.js-specific schema provider factory.
7 | * Creates {@link FileSystemSchemaProvider} with file system access.
8 | */
9 | export async function createSchemaProvider(
10 | workspaceRootUri: URI | undefined,
11 | logger: { info: (msg: string) => void; warn: (msg: string) => void; error: (msg: string) => void }
12 | ): Promise {
13 | try {
14 | logger.info('Running in Node.js environment, using FileSystemSchemaProvider');
15 | return new FileSystemSchemaProvider(workspaceRootUri || null, logger);
16 | } catch (error) {
17 | logger.error(`Failed to create FileSystemSchemaProvider: ${error}`);
18 | return undefined;
19 | }
20 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kson-lsp-extensions",
3 | "//": "[[kson-version-num]]",
4 | "version": "0.3.0-dev.0",
5 | "private": true,
6 | "description": "KSON Language Server Protocol Extensions",
7 | "workspaces": [
8 | "shared",
9 | "vscode",
10 | "monaco"
11 | ],
12 | "scripts": {
13 | "buildMonaco": "npm run compile && npm run build -w monaco",
14 | "buildVSCode": "npm run compile && npm run packagePlugin -w vscode",
15 | "buildPlugins": "npm run buildMonaco && npm run buildVSCode",
16 | "compile": "npm run compile -w shared && npm run compile -w vscode",
17 | "clean": "npm run clean --workspaces --if-present",
18 | "test": "npm run compile && npm run test --workspaces --if-present",
19 | "vscode": "npm run compile && npm run vscode -w vscode",
20 | "monaco": "npm run compile && npm run dev -w monaco"
21 | },
22 | "devDependencies": {
23 | "typescript": "^5.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/assets/logo/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # assuming the root image is kson_logo_transparent.svg we can regenerate the rest
4 |
5 | # First, add a background to the transparent SVG
6 | # Assuming the `g` node's last attirbute is `id="layer1"`, this will append a new rectangle (background)
7 | # just inside the group
8 | sed '/id="layer1">$/a\ ' kson_logo_transparent.svg > kson_logo_blue.svg
9 |
10 | # Now create png versions of the transparent and blue svgs
11 | inkscape kson_logo_transparent.svg --export-type=png --export-filename=kson_logo_transparent.png
12 | inkscape kson_logo_transparent.svg --export-type=png --export-width=800 --export-height=800 --export-filename=kson_logo_transparent_800x800.png
13 | inkscape kson_logo_blue.svg --export-type=png --export-filename=kson_logo_blue.png
14 | inkscape kson_logo_blue.svg --export-type=png --export-width=800 --export-height=800 --export-filename=kson_logo_blue_800x800.png
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/IfValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonValue
4 | import org.kson.parser.MessageSink
5 | import org.kson.schema.JsonSchema
6 | import org.kson.schema.JsonSchemaValidator
7 |
8 | class IfValidator(private val ifSchema: JsonSchema?, private val thenSchema: JsonSchema?, private val elseSchema: JsonSchema?) :
9 | JsonSchemaValidator {
10 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
11 | if (ifSchema == null) {
12 | return
13 | }
14 |
15 | val tmpMessageSink = MessageSink()
16 | if (ifSchema.isValid(ksonValue, tmpMessageSink)) {
17 | // if condition is true, run then schema if it exists
18 | thenSchema?.validate(ksonValue, messageSink)
19 | } else {
20 | // if condition is false, run else schema if it exists
21 | elseSchema?.validate(ksonValue, messageSink)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/RequiredValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonObject
4 | import org.kson.value.KsonString
5 | import org.kson.parser.MessageSink
6 | import org.kson.parser.messages.MessageType
7 | import org.kson.schema.JsonObjectValidator
8 |
9 | class RequiredValidator(private val required: List) : JsonObjectValidator() {
10 | override fun validateObject(node: KsonObject, messageSink: MessageSink) {
11 | val propertyNames = node.propertyMap.keys
12 | val missingProperties = required.filter { !propertyNames.contains(it.value) }
13 | if (missingProperties.isNotEmpty()) {
14 | val missingPropertyNames = missingProperties.joinToString(", ") { it.value }
15 | messageSink.error(
16 | node.location.trimToFirstLine(),
17 | MessageType.SCHEMA_REQUIRED_PROPERTY_MISSING.create(missingPropertyNames)
18 | )
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/schema/validators/ConstValidatorTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.schema.JsonSchemaTest
4 | import org.kson.parser.messages.MessageType.SCHEMA_VALUE_NOT_EQUAL_TO_CONST
5 | import kotlin.test.Test
6 | import kotlin.test.assertContains
7 |
8 | class ConstValidatorTest : JsonSchemaTest {
9 | @Test
10 | fun testErrorMessaging() {
11 | val constValue = "the const value"
12 | val errors = assertKsonSchemaErrors(
13 | """
14 | isConst: "this value is wrong"
15 | """.trimIndent(),
16 | """
17 | properties:
18 | isConst:
19 | const: "$constValue"
20 | """.trimIndent(),
21 | listOf(SCHEMA_VALUE_NOT_EQUAL_TO_CONST)
22 | )
23 |
24 | // ensure the error message refers to the required constant value
25 | assertContains(errors[0].message.toString(), constValue)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/createCommandExecutor.browser.ts:
--------------------------------------------------------------------------------
1 | import {Connection} from 'vscode-languageserver';
2 | import {KsonDocumentsManager} from '../document/KsonDocumentsManager.js';
3 | import {FormattingService} from '../features/FormattingService.js';
4 | import type {KsonSettings} from '../KsonSettings.js';
5 | import {CommandExecutor} from './CommandExecutor.browser.js';
6 |
7 | /**
8 | * Browser-specific command executor factory.
9 | * Creates CommandExecutor without file system support.
10 | */
11 | export function createCommandExecutor(
12 | connection: Connection,
13 | documentManager: KsonDocumentsManager,
14 | formattingService: FormattingService,
15 | getConfiguration: () => Required,
16 | workspaceRoot: string | null = null
17 | ): CommandExecutor {
18 | return new CommandExecutor(
19 | connection,
20 | documentManager,
21 | formattingService,
22 | getConfiguration,
23 | workspaceRoot
24 | );
25 | }
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/createCommandExecutor.node.ts:
--------------------------------------------------------------------------------
1 | import {Connection} from 'vscode-languageserver';
2 | import {KsonDocumentsManager} from '../document/KsonDocumentsManager.js';
3 | import {FormattingService} from '../features/FormattingService.js';
4 | import type {KsonSettings} from '../KsonSettings.js';
5 | import {CommandExecutor} from './CommandExecutor.node.js';
6 |
7 | /**
8 | * Node.js-specific command executor factory.
9 | * Creates CommandExecutor with full file system support.
10 | */
11 | export function createCommandExecutor(
12 | connection: Connection,
13 | documentManager: KsonDocumentsManager,
14 | formattingService: FormattingService,
15 | getConfiguration: () => Required,
16 | workspaceRoot: string | null = null
17 | ): CommandExecutor {
18 | return new CommandExecutor(
19 | connection,
20 | documentManager,
21 | formattingService,
22 | getConfiguration,
23 | workspaceRoot
24 | );
25 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/editor/KsonQuoteMatcherTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | class KsonQuoteMatcherTest : KsonEditorActionTest() {
4 | /**
5 | * Sanity check that [KsonQuoteHandler] is correctly hooked up for auto-inserting close quotes
6 | * This is the inverse operation to what is tested in [KsonBackspaceHandlerDelegateTest.testDeleteEmptyQuotePairs]
7 | */
8 | fun testAutoInsert() {
9 | doCharTest(
10 | "",
11 | '"',
12 | "\"\""
13 | )
14 |
15 | doCharTest(
16 | "",
17 | '\'',
18 | "''"
19 | )
20 | }
21 |
22 | fun testManualClose() {
23 | doCharTest(
24 | "\"",
25 | '"',
26 | "\"\""
27 | )
28 |
29 | doCharTest(
30 | "key: \"unclosed",
31 | '"',
32 | "key: \"unclosed\""
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/MultipleOfValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonNumber
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonNumberValidator
7 | import kotlin.math.abs
8 | import kotlin.math.max
9 |
10 | class MultipleOfValidator(private val multipleOf: Double) : JsonNumberValidator() {
11 | override fun validateNumber(node: KsonNumber, messageSink: MessageSink) {
12 | val number = node.value.asDouble
13 | if (multipleOf != 0.0) {
14 | val remainder = number % multipleOf
15 | val epsilon = 1e-10 * max(abs(number), abs(multipleOf))
16 | if (abs(remainder) > epsilon && abs(remainder - multipleOf) > epsilon) {
17 | messageSink.error(
18 | node.location,
19 | MessageType.SCHEMA_VALUE_MUST_BE_MULTIPLE_OF.create(multipleOf.toString())
20 | )
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/parser/messages/MessagesTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.parser.messages
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertContains
5 | import kotlin.test.assertFailsWith
6 |
7 | class MessagesTest {
8 | @Test
9 | fun testFormat() {
10 | // sanity check formatting is hooked up correctly by verifying we can find our
11 | // "tagNameForTest" embedded in the formatted string
12 | //
13 | // (NOTE: this naturally relies on assumption that EMBED_BLOCK_NO_CLOSE's formatted
14 | // message will always refer to its embed tag. Apologies if that changes and this fails)
15 | val message = MessageType.EMBED_BLOCK_NO_CLOSE.create("embedDelimiter")
16 | assertContains(message.toString(), "embedDelimiter")
17 | }
18 |
19 | @Test
20 | fun testFormatNullArgs() {
21 | assertFailsWith(IllegalArgumentException::class, "should blow up on null argument") {
22 | MessageType.EMBED_BLOCK_NO_CLOSE.create(null)
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/lib-python/uvw.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | rem Simple uv wrapper for Windows that auto-installs uv locally if needed
4 |
5 | set UV_DIR=%cd%\.uv
6 | set UV_BIN=%UV_DIR%\uv.exe
7 |
8 | rem Install uv locally if not present
9 | if not exist "%UV_BIN%" (
10 | echo Installing uv locally to %UV_DIR%...
11 |
12 | rem Use uv's official installation script with custom install location
13 | set UV_UNMANAGED_INSTALL=%UV_DIR%
14 |
15 | rem Use pwsh if available, because legacy powershell causes problems when launched from pwsh!
16 | where pwsh >nul 2>&1
17 | if %errorlevel% equ 0 (
18 | pwsh -NoProfile -ExecutionPolicy ByPass -Command "iwr -useb https://astral.sh/uv/install.ps1 | iex"
19 | ) else (
20 | powershell -NoProfile -ExecutionPolicy ByPass -Command "iwr -useb https://astral.sh/uv/install.ps1 | iex"
21 | )
22 |
23 | if not exist "%UV_BIN%" (
24 | echo Failed to install uv
25 | exit /b 1
26 | )
27 | )
28 |
29 | rem Execute uv with all arguments
30 | "%UV_BIN%" %*
31 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/suite/assert.ts:
--------------------------------------------------------------------------------
1 | // Browser-compatible assert implementation
2 | export const assert = {
3 | strictEqual(actual: T, expected: T, message?: string): void {
4 | if (actual !== expected) {
5 | throw new Error(message || `Expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
6 | }
7 | },
8 |
9 | ok(value: any, message?: string): void {
10 | if (!value) {
11 | throw new Error(message || `Expected truthy value but got ${JSON.stringify(value)}`);
12 | }
13 | },
14 |
15 | fail(message?: string): void {
16 | throw new Error(message || 'Assertion failed');
17 | },
18 |
19 | deepStrictEqual(actual: T, expected: T, message?: string): void {
20 | const actualStr = JSON.stringify(actual);
21 | const expectedStr = JSON.stringify(expected);
22 | if (actualStr !== expectedStr) {
23 | throw new Error(message || `Expected ${expectedStr} but got ${actualStr}`);
24 | }
25 | }
26 | };
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/folding/KsonFoldingTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.folding
2 |
3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase
4 |
5 | class KsonFoldingTest : BasePlatformTestCase() {
6 | override fun getTestDataPath(): String = "src/test/resources/testData/folding"
7 |
8 | override fun setUp() {
9 | super.setUp()
10 | myFixture.testDataPath = testDataPath
11 | }
12 |
13 | fun testDashListFolding() {
14 | doTest()
15 | }
16 |
17 | fun testBracketListFolding() {
18 | doTest()
19 | }
20 |
21 | fun testAngleListFolding() {
22 | doTest()
23 | }
24 |
25 | fun testEmbedBlockFolding() {
26 | doTest()
27 | }
28 |
29 | fun testSingleLineShouldNotFold() {
30 | doTest()
31 | }
32 |
33 | fun testObjectFolding() {
34 | doTest()
35 | }
36 |
37 | private fun doTest() {
38 | val testName = getTestName(true)
39 | myFixture.testFolding("$testDataPath/$testName.kson")
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/resources/kson/example.kson:
--------------------------------------------------------------------------------
1 | # Example KSON file
2 | # This is a comment
3 |
4 | # Simple key-value pairs
5 | name: "John Doe"
6 | age: 30
7 | active: true
8 |
9 | # Nested objects
10 | address: {
11 | street: "123 Main St"
12 | city: "New York"
13 | zipCode: "10001"
14 | }
15 |
16 | # Arrays
17 | hobbies: [
18 | "reading"
19 | "swimming"
20 | "coding"
21 | ]
22 |
23 | # Mixed array
24 | mixedData: [
25 | 42
26 | "hello"
27 | true
28 | { nested: "value" }
29 | ]
30 |
31 | # Multi-line strings with embed blocks
32 | description: $
33 | This is a multi-line
34 | string example in KSON
35 | $$
36 |
37 | # Code embed block with language tag
38 | script: %javascript
39 | function hello() {
40 | console.log("Hello from KSON!");
41 | }
42 | %%
43 |
44 | # Complex nested structure
45 | project: {
46 | name: "KSON Editor"
47 | version: "1.0.0"
48 | dependencies: {
49 | "monaco-editor": "^0.45.0"
50 | "vscode-languageclient": "^9.0.0"
51 | }
52 | scripts: {
53 | build: "npm run compile"
54 | test: "npm test"
55 | }
56 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/editor/KsonPairedBraceMatcherTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | class KsonPairedBraceMatcherTest : KsonEditorActionTest() {
4 | /**
5 | * Sanity check that [KsonPairedBraceMatcher] is correctly hooked up
6 | */
7 | fun testAutoInsert() {
8 | withConfigSetting(ConfigProperty.AUTOINSERT_PAIR_BRACKET(), true) {
9 | doCharTest(
10 | "",
11 | '{',
12 | "{}"
13 | )
14 |
15 | doCharTest(
16 | "",
17 | '[',
18 | "[]"
19 | )
20 | }
21 |
22 | withConfigSetting(ConfigProperty.AUTOINSERT_PAIR_BRACKET(), false) {
23 | doCharTest(
24 | "",
25 | '{',
26 | "{"
27 | )
28 |
29 | doCharTest(
30 | "",
31 | '[',
32 | "["
33 | )
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kson/monaco-editor",
3 | "version": "1.0.0",
4 | "description": "Monaco editor with KSON language support and LSP integration",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "test": "playwright test",
11 | "test:headed": "playwright test --headed"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@codingame/esbuild-import-meta-url-plugin": "^1.0.3",
17 | "@codingame/monaco-vscode-typescript-basics-default-extension": "~18.1.0",
18 | "@codingame/monaco-vscode-javascript-default-extension": "~18.1.0",
19 | "@codingame/monaco-vscode-sql-default-extension": "~18.1.0",
20 | "@codingame/monaco-vscode-python-default-extension": "~18.1.0",
21 | "@kson/lsp-shared": "file:../shared",
22 | "monaco-editor-wrapper": "^6.9.0",
23 | "vscode-languageserver": "^9.0.1"
24 | },
25 | "devDependencies": {
26 | "@playwright/test": "^1.53.2",
27 | "vite": "^7.0.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/UniqueItemsValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonList
4 | import org.kson.value.KsonValue
5 | import org.kson.parser.MessageSink
6 | import org.kson.parser.messages.MessageType
7 | import org.kson.schema.JsonArrayValidator
8 |
9 | class UniqueItemsValidator(private val uniqueItems: Boolean) : JsonArrayValidator() {
10 | override fun validateArray(node: KsonList, messageSink: MessageSink) {
11 | if (uniqueItems && !areItemsUnique(node.elements)) {
12 | messageSink.error(node.location, MessageType.SCHEMA_ARRAY_ITEMS_NOT_UNIQUE.create())
13 | }
14 | }
15 |
16 | /**
17 | * Check if all items in a list are unique using JSON Schema equality semantics.
18 | */
19 | private fun areItemsUnique(elements: List): Boolean {
20 | val seen = mutableSetOf()
21 | for (element in elements) {
22 | if (!seen.add(element)) {
23 | return false
24 | }
25 | }
26 | return true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/CommandType.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum defining all available commands in the Kson Language Server
3 | */
4 | export enum CommandType {
5 | /**
6 | * Format the document as plain Kson
7 | */
8 | PLAIN_FORMAT = 'kson.plainFormat',
9 |
10 | /**
11 | * Format the document as delimited Kson
12 | */
13 | DELIMITED_FORMAT = 'kson.delimitedFormat',
14 |
15 | /**
16 | * Format the document as compact Kson
17 | */
18 | COMPACT_FORMAT = 'kson.compactFormat',
19 |
20 | /**
21 | * Format the document as classic Kson
22 | */
23 | CLASSIC_FORMAT = 'kson.classicFormat',
24 |
25 | /**
26 | * Associate a schema with the current document
27 | */
28 | ASSOCIATE_SCHEMA = 'kson.associateSchema',
29 |
30 | /**
31 | * Remove schema association from the current document
32 | */
33 | REMOVE_SCHEMA = 'kson.removeSchema'
34 | }
35 |
36 | /**
37 | * Get all available command IDs
38 | */
39 | export function getAllCommandIds(): string[] {
40 | return Object.values(CommandType);
41 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/src/config/clientOptions.ts:
--------------------------------------------------------------------------------
1 | import vscode from 'vscode';
2 | import {
3 | LanguageClientOptions,
4 | ErrorAction,
5 | CloseAction,
6 | } from 'vscode-languageclient';
7 |
8 | // Create shared client options
9 | export const createClientOptions = (outputChannel: vscode.OutputChannel): LanguageClientOptions => {
10 |
11 | return {
12 | documentSelector: [
13 | {scheme: 'file', language: 'kson'},
14 | {scheme: 'untitled', language: 'kson'}
15 | ],
16 | synchronize: {
17 | /**
18 | * TODO - Even though this setting is deprecated it is the easiest way to get configuration going.
19 | * We should find a way in the future to replace this with the /pull model.
20 | */
21 | configurationSection: 'kson',
22 | fileEvents: vscode.workspace.createFileSystemWatcher('**/*.kson')
23 | },
24 | outputChannel,
25 | errorHandler: {
26 | error: () => ({action: ErrorAction.Continue}),
27 | closed: () => ({action: CloseAction.DoNotRestart})
28 | }
29 | };
30 | }
--------------------------------------------------------------------------------
/kson-lib/pixi.lock:
--------------------------------------------------------------------------------
1 | version: 6
2 | environments:
3 | default:
4 | channels:
5 | - url: https://conda.anaconda.org/conda-forge/
6 | packages:
7 | win-64:
8 | - conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_31.conda
9 | - conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda
10 | packages:
11 | - conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_31.conda
12 | sha256: c0a380608afc9ad98b1e261fe55702b72e33f80d372f358075c5dea3d185816f
13 | md5: 0bf228856c72261e5bbd9530002176f2
14 | depends:
15 | - vswhere
16 | constrains:
17 | - vs_win-64 2022.14
18 | track_features:
19 | - vc14
20 | license: BSD-3-Clause
21 | license_family: BSD
22 | size: 21054
23 | timestamp: 1753739201422
24 | - conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda
25 | sha256: b72270395326dc56de9bd6ca82f63791b3c8c9e2b98e25242a9869a4ca821895
26 | md5: f622897afff347b715d046178ad745a5
27 | depends:
28 | - __win
29 | license: MIT
30 | license_family: MIT
31 | size: 238764
32 | timestamp: 1745560912727
33 |
--------------------------------------------------------------------------------
/tooling/cli/pixi.lock:
--------------------------------------------------------------------------------
1 | version: 6
2 | environments:
3 | default:
4 | channels:
5 | - url: https://conda.anaconda.org/conda-forge/
6 | packages:
7 | win-64:
8 | - conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_31.conda
9 | - conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda
10 | packages:
11 | - conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_31.conda
12 | sha256: c0a380608afc9ad98b1e261fe55702b72e33f80d372f358075c5dea3d185816f
13 | md5: 0bf228856c72261e5bbd9530002176f2
14 | depends:
15 | - vswhere
16 | constrains:
17 | - vs_win-64 2022.14
18 | track_features:
19 | - vc14
20 | license: BSD-3-Clause
21 | license_family: BSD
22 | size: 21054
23 | timestamp: 1753739201422
24 | - conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda
25 | sha256: b72270395326dc56de9bd6ca82f63791b3c8c9e2b98e25242a9869a4ca821895
26 | md5: f622897afff347b715d046178ad745a5
27 | depends:
28 | - __win
29 | license: MIT
30 | license_family: MIT
31 | size: 238764
32 | timestamp: 1745560912727
33 |
--------------------------------------------------------------------------------
/lib-python/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include build_backend.py
2 | include pyproject.toml
3 | include README.md
4 | recursive-include src *.py
5 | recursive-include src *.c
6 |
7 | # Include necessary Gradle files from kson-sdist
8 | recursive-include kson-sdist *.kts *.kt *.properties *.md
9 | # Include Python source files from kson-sdist
10 | recursive-include kson-sdist *.py
11 | include kson-sdist/.circleci/config.kson
12 | include kson-sdist/gradlew
13 | include kson-sdist/gradlew.bat
14 | include kson-sdist/buildSrc/gradlew
15 | include kson-sdist/buildSrc/gradlew.bat
16 | include kson-sdist/gradle/wrapper/gradle-wrapper.jar
17 | include kson-sdist/gradle/wrapper/gradle-wrapper.properties
18 | include kson-sdist/buildSrc/gradle/wrapper/gradle-wrapper.jar
19 | include kson-sdist/buildSrc/gradle/wrapper/gradle-wrapper.properties
20 |
21 | # Explicitly exclude all build artifacts
22 | prune kson-sdist/**/.gradle
23 | prune kson-sdist/**/build
24 | prune kson-sdist/**/.kotlin
25 | global-exclude __pycache__
26 | global-exclude *.py[co]
27 | global-exclude .DS_Store
28 | global-exclude *.class
29 | global-exclude *.lock
30 | global-exclude *.bin
31 | global-exclude *.hprof
32 | global-exclude *.log
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/stdlibx/collections/Immutable.kt:
--------------------------------------------------------------------------------
1 | package org.kson.stdlibx.collections
2 |
3 | /**
4 | * Some useful immutable collection extensions from https://stackoverflow.com/a/37936456
5 | */
6 |
7 | class ImmutableList private constructor(private val inner: List) : List by inner {
8 | companion object {
9 | fun create(inner: List) = if (inner is ImmutableList) {
10 | inner
11 | } else {
12 | ImmutableList(inner.toList())
13 | }
14 | }
15 | }
16 |
17 | class ImmutableMap private constructor(private val inner: Map) : Map by inner {
18 | companion object {
19 | fun create(inner: Map) = if (inner is ImmutableMap) {
20 | inner
21 | } else {
22 | ImmutableMap(hashMapOf(*inner.toList().toTypedArray()))
23 | }
24 | }
25 | }
26 |
27 | fun Map.toImmutableMap(): ImmutableMap = ImmutableMap.create(this)
28 | fun List.toImmutableList(): ImmutableList = ImmutableList.create(this)
29 | fun Set.toImmutableList(): ImmutableList = ImmutableList.create(this.toList())
30 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/editor/KsonCompletionContributor.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | import com.intellij.codeInsight.completion.CompletionContributor
4 | import com.intellij.codeInsight.completion.CompletionInitializationContext
5 | import com.intellij.codeInsight.completion.CompletionType
6 | import com.intellij.patterns.PlatformPatterns.psiElement
7 | import org.kson.jetbrains.parser.elem
8 | import org.kson.parser.TokenType
9 |
10 | /**
11 | * Provides completion for language tags in KSON embed blocks.
12 | * Supports both custom language providers and built-in injectable languages.
13 | */
14 | class KsonCompletionContributor : CompletionContributor() {
15 | init {
16 | extend(
17 | CompletionType.BASIC,
18 | psiElement().withElementType(elem(TokenType.EMBED_TAG)),
19 | EmbedBlockLanguageListCompletionProvider()
20 | )
21 | }
22 |
23 | override fun beforeCompletion(context: CompletionInitializationContext) {
24 | // Use a newline in the dummy identifier to prevent it from being parsed as part of the tag
25 | context.dummyIdentifier = "${CompletionInitializationContext.DUMMY_IDENTIFIER}\n"
26 | }
27 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/highlighter/KsonSemanticHighlightAnnotator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.highlighter
2 |
3 | import com.intellij.lang.annotation.AnnotationHolder
4 | import com.intellij.lang.annotation.Annotator
5 | import com.intellij.lang.annotation.HighlightSeverity
6 | import com.intellij.openapi.project.DumbAware
7 | import com.intellij.psi.PsiElement
8 | import org.kson.jetbrains.parser.elem
9 | import org.kson.parser.ParsedElementType
10 |
11 | /**
12 | * [KsonSemanticHighlightAnnotator] provides semantic highlighting for Kson files.
13 | *
14 | * Since we don't require any indices we can implement [DumbAware]
15 | */
16 | class KsonSemanticHighlightAnnotator : Annotator, DumbAware {
17 | override fun annotate(element: PsiElement, holder: AnnotationHolder) {
18 | // Implement syntax highlighter that distinguishes regular strings from object keys.
19 | if (elem(ParsedElementType.OBJECT_KEY) == element.node.elementType){
20 | holder.newSilentAnnotation(HighlightSeverity.INFORMATION).textAttributes(
21 | KsonSyntaxHighlighter.getTextAttributesKey(KsonSyntaxHighlighter.KsonColorTag.KSON_OBJECT_KEY)
22 | ).create()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/util/DocumentExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.util
2 |
3 | import com.intellij.openapi.editor.Document
4 | import com.intellij.openapi.util.TextRange
5 | import org.kson.tools.IndentFormatter
6 | import org.kson.tools.IndentType
7 |
8 | /**
9 | * Calculates the indentation level of a specific line in the document.
10 | *
11 | * @param lineNumber The line number to analyze (0-based)
12 | * @param indentType The type of indentation to consider (e.g., spaces or tabs)
13 | * @return The number of indentation levels for the specified line
14 | */
15 | fun Document.getLineIndentLevel(lineNumber: Int, indentType: IndentType): Int {
16 | val prevLine = if (lineNumber > 0) {
17 | val lineStart = this.getLineStartOffset(lineNumber - 1)
18 | val lineEnd = this.getLineEndOffset(lineNumber - 1)
19 | this.getText(TextRange(lineStart, lineEnd))
20 | } else {
21 | ""
22 | }
23 |
24 | val lineStart = this.getLineStartOffset(lineNumber)
25 | val lineEnd = this.getLineEndOffset(lineNumber)
26 | val currentLine = this.getText(TextRange(lineStart, lineEnd))
27 |
28 | return IndentFormatter(indentType).getCurrentLineIndentLevel(prevLine, currentLine)
29 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/psi/KsonEmbedBlock.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.psi
2 |
3 | import com.intellij.lang.ASTNode
4 | import com.intellij.psi.util.elementType
5 | import org.kson.jetbrains.parser.elem
6 | import org.kson.parser.TokenType
7 | import org.kson.parser.behavior.embedblock.EmbedDelim
8 | import org.kson.stdlibx.exceptions.ShouldNotHappenException
9 |
10 | class KsonEmbedBlock(node: ASTNode) : KsonPsiElement(node) {
11 | val embedBlockTag: String
12 | get() = node.findChildByType(elem(TokenType.EMBED_TAG))?.text ?: ""
13 |
14 | val embedDelim: EmbedDelim
15 | get() = getDelim(this)
16 |
17 | val embedContent: KsonEmbedContent?
18 | get() = node.findChildByType(elem(TokenType.EMBED_CONTENT))?.psi as? KsonEmbedContent
19 |
20 |
21 | companion object {
22 | private fun getDelim(host: KsonEmbedBlock): EmbedDelim {
23 | val embedDelimText =
24 | host.children.find { it.elementType == elem(TokenType.EMBED_OPEN_DELIM) }?.text
25 | ?: throw ShouldNotHappenException("since this element got parsed as embed block we expect an EMBED_OPEN_DELIM")
26 | return EmbedDelim.fromString(embedDelimText)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kson/lsp-shared",
3 | "version": "0.0.1",
4 | "description": "Shared configurations and utilities for KSON LSP extensions",
5 | "type": "module",
6 | "main": "./dist/src/index.js",
7 | "types": "./dist/src/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/src/index.d.ts",
11 | "default": "./dist/src/index.js"
12 | },
13 | "./config": {
14 | "types": "./dist/src/config/index.d.ts",
15 | "default": "./dist/src/config/index.js"
16 | },
17 | "./connection": {
18 | "types": "./dist/src/connection/index.d.ts",
19 | "default": "./dist/src/connection/index.js"
20 | }
21 | },
22 | "scripts": {
23 | "compile": "tsc",
24 | "clean": "rm -rf dist",
25 | "generate:tmlanguage": "tsx scripts/generate-tm-embed-block.ts"
26 | },
27 | "keywords": [
28 | "kson",
29 | "lsp",
30 | "shared"
31 | ],
32 | "dependencies": {
33 | "kson-language-server": "file:../../language-server-protocol",
34 | "vscode-languageclient": "^9.0.1",
35 | "vscode-languageserver": "^9.0.1"
36 | },
37 | "devDependencies": {
38 | "@types/node": "^20.0.0",
39 | "tsx": "^4.20.3",
40 | "typescript": "^5.0.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/parser/behavior/StringUnquoted.kt:
--------------------------------------------------------------------------------
1 | package org.kson.parser.behavior
2 |
3 | /**
4 | * Behaviors and rules around unquoted Kson strings
5 | */
6 | object StringUnquoted {
7 |
8 | private val reservedKeywords = setOf("true", "false", "null")
9 |
10 | /**
11 | * Returns true if the given string may be used without quotes in a Kson document
12 | */
13 | fun isUnquotable(str: String): Boolean {
14 | return !reservedKeywords.contains(str) && str.isNotBlank() && str.withIndex().all { (index, letter) ->
15 | if (index == 0) {
16 | isUnquotedStartChar(letter)
17 | } else {
18 | isUnquotedBodyChar(letter)
19 | }
20 | }
21 | }
22 |
23 | /**
24 | * Returns true if [ch] is a legal first [Char] for an unquoted Kson string
25 | */
26 | fun isUnquotedStartChar(ch: Char?): Boolean {
27 | ch ?: return false
28 | return ch.isLetter() || (ch == '_')
29 | }
30 |
31 | /**
32 | * Returns true if [ch] is a legal [Char] for the body of an unquoted Kson string
33 | */
34 | fun isUnquotedBodyChar(ch: Char?): Boolean {
35 | ch ?: return false
36 | return isUnquotedStartChar(ch) || ch.isDigit()
37 | }
38 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/schema/validators/AllOfValidatorTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.parser.messages.MessageType.*
4 | import org.kson.schema.JsonSchemaTest
5 | import kotlin.test.Test
6 |
7 | class AllOfValidatorTest : JsonSchemaTest {
8 | @Test
9 | fun testAllOfValidationErrors() {
10 | assertKsonSchemaErrors(
11 | """
12 | description: 99
13 | think: false
14 | """.trimIndent(),
15 | """
16 | allOf:
17 | - properties:
18 | description:
19 | type: string
20 | .
21 | thing:
22 | type: number
23 |
24 | - required: [other_thing]
25 | properties:
26 | description:
27 | type: string
28 | """.trimIndent(),
29 | listOf(
30 | // description is wrong type according to both schemas
31 | SCHEMA_VALUE_TYPE_MISMATCH,
32 | // missing 'other_thing' required by second schema
33 | SCHEMA_REQUIRED_PROPERTY_MISSING)
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/util/PsiFileExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.util
2 |
3 | import com.intellij.application.options.CodeStyle
4 | import com.intellij.psi.PsiElement
5 | import com.intellij.psi.PsiFile
6 | import com.intellij.psi.tree.IElementType
7 | import com.intellij.psi.util.elementType
8 | import com.intellij.psi.util.findParentInFile
9 | import org.kson.tools.IndentType
10 |
11 | /**
12 | * Returns true if the given [offset] within this [PsiFile] lands inside a [PsiElement] of one of the types
13 | * given in [elements]
14 | */
15 | fun PsiFile.hasElementAtOffset(offset: Int, elements: Set): Boolean {
16 | val elemAtFirstDelimChar = this.findElementAt(offset) ?: return false
17 | val parentFound = elemAtFirstDelimChar.findParentInFile(true) {
18 | elements.contains(it.elementType)
19 | }
20 | return parentFound != null
21 | }
22 |
23 | /**
24 | * Gets the indent type (tabs or spaces) configured for this file
25 | */
26 | fun PsiFile.getIndentType(): IndentType {
27 | val indentOptions = CodeStyle.getIndentOptions(this)
28 | return if (indentOptions.USE_TAB_CHARACTER) {
29 | IndentType.Tab()
30 | } else {
31 | IndentType.Space(indentOptions.INDENT_SIZE)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/editor/KsonPairedBraceMatcher.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | import com.intellij.lang.BracePair
4 | import com.intellij.lang.PairedBraceMatcher
5 | import com.intellij.psi.PsiFile
6 | import com.intellij.psi.tree.IElementType
7 | import org.kson.jetbrains.parser.elem
8 | import org.kson.parser.TokenType
9 |
10 | private val bracePairs = arrayOf(
11 | BracePair(
12 | elem(TokenType.CURLY_BRACE_L),
13 | elem(TokenType.CURLY_BRACE_R),
14 | true
15 | ),
16 | BracePair(
17 | elem(TokenType.SQUARE_BRACKET_L),
18 | elem(TokenType.SQUARE_BRACKET_R),
19 | true
20 | ),
21 | BracePair(
22 | elem(TokenType.ANGLE_BRACKET_L),
23 | elem(TokenType.ANGLE_BRACKET_R),
24 | true
25 | )
26 | )
27 |
28 | class KsonPairedBraceMatcher : PairedBraceMatcher {
29 |
30 | override fun getPairs(): Array {
31 | return bracePairs
32 | }
33 |
34 | override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?): Boolean {
35 | return true
36 | }
37 |
38 | override fun getCodeConstructStart(file: PsiFile?, openingBraceOffset: Int): Int {
39 | return openingBraceOffset
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/schema/SchemaProvider.ts:
--------------------------------------------------------------------------------
1 | import {TextDocument} from 'vscode-languageserver-textdocument';
2 | import {DocumentUri} from 'vscode-languageserver';
3 |
4 | /**
5 | * Interface for providing schemas in different environments (Node.js, Browser).
6 | * This abstraction allows the schema system to work without direct file system access.
7 | */
8 | export interface SchemaProvider {
9 | /**
10 | * Get the schema document for a given KSON document URI.
11 | *
12 | * @param documentUri The URI of the KSON document
13 | * @returns TextDocument containing the schema, or undefined if no schema is available
14 | */
15 | getSchemaForDocument(documentUri: DocumentUri): TextDocument | undefined;
16 |
17 | /**
18 | * Reload the schema configuration.
19 | * Should be called when configuration changes are detected.
20 | */
21 | reload(): void;
22 | }
23 |
24 | /**
25 | * A no-op schema provider that returns undefined for all requests.
26 | * Used as a fallback when no schema configuration is available.
27 | */
28 | export class NoOpSchemaProvider implements SchemaProvider {
29 | getSchemaForDocument(_documentUri: DocumentUri): TextDocument | undefined {
30 | return undefined;
31 | }
32 |
33 | reload(): void {
34 | // No-op
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tooling/jetbrains/gradle.properties:
--------------------------------------------------------------------------------
1 | # IntelliJ Platform Artifacts Repositories
2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
3 |
4 | pluginGroup = org.kson.jetbrains
5 | pluginName = KSON
6 |
7 | # [[kson-version-num]]
8 | pluginVersion = 0.3.0-SNAPSHOT
9 |
10 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
11 | # for insight into build numbers and IntelliJ Platform versions.
12 | pluginSinceBuild = 223.8
13 | pluginUntilBuild = 252.*
14 |
15 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
16 | platformType = IC
17 | platformVersion = 2022.3.3
18 |
19 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
20 | # Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP
21 | platformPlugins = org.intellij.intelliLang
22 | # Example: platformBundledPlugins = com.intellij.java
23 | platformBundledPlugins =
24 |
25 | # Java language level used to compile sources and to generate the files
26 | # Using Java 17 is required since 2022.3
27 | javaVersion = 17
28 |
29 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
30 | org.gradle.configuration-cache = true
31 |
32 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
33 | org.gradle.caching = true
34 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/readme.md:
--------------------------------------------------------------------------------
1 | ## Language Server Protocol Clients
2 |
3 | This project contains clients of the [Language Server Protocol](../language-server-protocol). Currently, we have
4 | a [Monaco](./monaco) client and a [VS Code](./vscode) client.
5 | The monaco clients can be run with the following gradlew tasks:
6 |
7 | ```shell
8 | ./gradlew tooling:lsp-clients:npm_run_monaco
9 | ./gradlew tooling:lsp-clients:npm_run_vscode
10 | ```
11 |
12 | ## Features
13 |
14 | - Syntax highlighting for `.kson` files
15 | - Language server integration
16 | - Auto-formatting
17 | - Diagnostics and error reporting
18 |
19 | ### Development
20 |
21 | The clients start a KSON Language Server. If we are on the browser runtime (Monaco or VS Code web) the server is started
22 | in a webworker. If we are on a node runtime the server is started in its own process.
23 |
24 | Since there is a distinction between node and browser runtimes within the VS Code client this client has both a `./node`
25 | and `./browser` directory for the client and server. The Monaco client only uses the browser runtime.
26 |
27 | `./shared` contains shared code for both clients. Currently, these are
28 | the [textmate grammar](./shared/extension/config/kson.tmLanguage.json)
29 | , [language configuration](./shared/extension/config/language-configuration.json), and the code
30 | to [start a server](./shared/src/connection/browserConnection.ts) for the browser runtime.
31 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/RefValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonValue
4 | import org.kson.parser.MessageSink
5 | import org.kson.schema.*
6 |
7 | /**
8 | * Validator for JSON Schema `$ref` references
9 | *
10 | * @param [resolvedRef] the [ResolvedRef] object for this $ref
11 | * @param [idLookup] the IdSchemaLookup for resolving nested $ref references within the referenced schema
12 | */
13 | class RefValidator(
14 | private val resolvedRef: ResolvedRef,
15 | private val idLookup: SchemaIdLookup
16 | ) : JsonSchemaValidator {
17 | private val refSchema: JsonSchema? by lazy {
18 | // Parse the resolved value as a schema with the appropriate base URI context
19 | val messageSink = MessageSink()
20 | // TODO these parsed $ref schemas should be cached for efficiency
21 | SchemaParser.parseSchemaElement(
22 | resolvedRef.resolvedValue,
23 | messageSink,
24 | resolvedRef.resolvedValueBaseUri,
25 | idLookup)
26 | }
27 |
28 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
29 | val schema = refSchema
30 | ?: // Schema parsing failed, so can't perform validation against it
31 | return
32 |
33 | // Validate the value against our referenced schema
34 | schema.validate(ksonValue, messageSink)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/shared/src/connection/browserConnection.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BrowserMessageReader,
3 | BrowserMessageWriter,
4 | } from 'vscode-languageserver/browser';
5 | import { createConnection } from 'vscode-languageserver/browser';
6 | import { startKsonServer } from 'kson-language-server/browser';
7 |
8 | /**
9 | * Creates browser-specific message reader and writer.
10 | * This is shared between VS Code Web and Monaco Editor.
11 | */
12 | function createBrowserMessageTransports(context: Window & typeof globalThis) {
13 | const messageReader = new BrowserMessageReader(context);
14 | const messageWriter = new BrowserMessageWriter(context);
15 |
16 | return { messageReader, messageWriter };
17 | }
18 |
19 | /**
20 | * Sets up console logging to use a connection.
21 | * Shared utility for redirecting console output to LSP connection.
22 | */
23 | function setupConnectionLogging(connection: { console: { log: Function, error: Function } }) {
24 | console.log = connection.console.log.bind(connection.console);
25 | console.error = connection.console.error.bind(connection.console);
26 | }
27 |
28 | export function createAndStartBrowserWorker(){
29 |
30 | const { messageReader, messageWriter } = createBrowserMessageTransports(self);
31 | const connection = createConnection(messageReader, messageWriter);
32 |
33 | setupConnectionLogging(connection);
34 |
35 | startKsonServer(connection);
36 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/parser/ParserTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.parser
2 |
3 | import org.kson.ast.AstNode
4 | import org.kson.CompileTarget
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertNotNull
8 | import kotlin.test.assertTrue
9 |
10 | /**
11 | * NOTE: most Kson parsing tests are done the more holistic level in [org.kson.KsonCoreTest]. If/when we have
12 | * a need to test specifically at the [Parser] class level, those tests belong here.
13 | */
14 | class ParserTest {
15 |
16 | /**
17 | * NOTE: testing [Parser] directly rarely makes sense, and this is mostly a placeholder test.
18 | * Test in [org.kson.KsonCoreTest] instead. See class level doc above for more detail.
19 | */
20 | @Test
21 | fun testSanityCheckParse() {
22 | val nullTokenStream = listOf(
23 | Token(TokenType.NULL, Lexeme("null", Location.create(0, 0, 0, 4, 0, 4)), "null"),
24 | Token(TokenType.EOF, Lexeme("", Location.create(0, 4, 0, 4, 4, 4)), "")
25 | )
26 | val builder = KsonBuilder(nullTokenStream)
27 | Parser(builder).parse()
28 | val messageSink = MessageSink()
29 | val ksonRoot = builder.buildTree(messageSink)
30 | assertNotNull(ksonRoot)
31 | assertTrue(messageSink.loggedMessages().isEmpty())
32 | assertEquals(ksonRoot.toSource(AstNode.Indent(), CompileTarget.Kson()), "null")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/editor/EmbedBlockLanguageListCompletionProvider.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.editor
2 |
3 | import com.intellij.codeInsight.completion.*
4 | import com.intellij.codeInsight.lookup.LookupElementBuilder
5 | import com.intellij.lang.Language
6 | import com.intellij.lang.LanguageUtil
7 | import com.intellij.openapi.util.text.StringUtil
8 | import com.intellij.ui.DeferredIconImpl
9 | import com.intellij.util.ProcessingContext
10 | import javax.swing.Icon
11 |
12 | internal class EmbedBlockLanguageListCompletionProvider : CompletionProvider() {
13 | override fun addCompletions(
14 | parameters: CompletionParameters,
15 | context: ProcessingContext,
16 | result: CompletionResultSet
17 | ) {
18 | for (language in LanguageUtil.getInjectableLanguages()) {
19 | val lookupElement = LookupElementBuilder.create(StringUtil.toLowerCase(language.id))
20 | .withIcon(createLanguageIcon(language))
21 | .withTypeText(language.displayName, true)
22 | result.addElement(lookupElement)
23 | }
24 | }
25 |
26 | companion object {
27 | fun createLanguageIcon(language: Language): Icon {
28 | return DeferredIconImpl(null, language, true) { curLanguage: Language ->
29 | curLanguage.associatedFileType?.icon
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/ConstValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.value.KsonValue
4 | import org.kson.parser.MessageSink
5 | import org.kson.parser.messages.MessageType
6 | import org.kson.schema.JsonSchemaValidator
7 | import org.kson.value.EmbedBlock
8 | import org.kson.value.KsonBoolean
9 | import org.kson.value.KsonList
10 | import org.kson.value.KsonNull
11 | import org.kson.value.KsonNumber
12 | import org.kson.value.KsonObject
13 | import org.kson.value.KsonString
14 |
15 | class ConstValidator(private val const: KsonValue) : JsonSchemaValidator {
16 | override fun validate(ksonValue: KsonValue, messageSink: MessageSink) {
17 | if (ksonValue != const) {
18 | val requiredValue = when (const) {
19 | is KsonNull -> "null"
20 | is KsonBoolean -> const.value.toString()
21 | is KsonNumber -> const.value.asString
22 | is KsonString -> const.value
23 | // TODO ksonValue should be able to serialize to its corresponding KSON
24 | is KsonObject -> "the schema-specified object"
25 | is KsonList -> "the schema-specified list"
26 | is EmbedBlock -> "the schema-specified embed block"
27 | }
28 | messageSink.error(ksonValue.location, MessageType.SCHEMA_VALUE_NOT_EQUAL_TO_CONST.create(requiredValue))
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/KsonCoreTestEmbedBlockError.kt:
--------------------------------------------------------------------------------
1 | package org.kson
2 |
3 | import org.kson.parser.messages.MessageType.*
4 | import kotlin.test.Test
5 |
6 | class KsonCoreTestEmbedBlockError : KsonCoreTestError {
7 | /**
8 | * Regression test for a parsing problem we had at the boundary:
9 | * this was blowing up with an index out of bounds rather than
10 | * parsing with an [EMBED_BLOCK_NO_CLOSE] error as it should
11 | */
12 | @Test
13 | fun testUnclosedEmbedWithEscape() {
14 | assertParserRejectsSource(
15 | """
16 | %%
17 | %\%
18 | """.trimIndent(),
19 | listOf(EMBED_BLOCK_NO_CLOSE)
20 | )
21 | }
22 |
23 | @Test
24 | fun testUnclosedEmbedDelimiterError() {
25 | assertParserRejectsSource("%%\n", listOf(EMBED_BLOCK_NO_CLOSE))
26 | }
27 |
28 | @Test
29 | fun testUnclosedEmbedAlternateDelimiterError() {
30 | assertParserRejectsSource("$\n", listOf(EMBED_BLOCK_NO_CLOSE))
31 | }
32 |
33 | @Test
34 | fun testEmbedBlockPartialDelim() {
35 | assertParserRejectsSource(
36 | """
37 | %
38 | """,
39 | listOf(EMBED_BLOCK_NO_CLOSE)
40 | )
41 |
42 | assertParserRejectsSource(
43 | """
44 | $
45 | """,
46 | listOf(EMBED_BLOCK_NO_CLOSE)
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/CommandExecutor.browser.ts:
--------------------------------------------------------------------------------
1 | import {CommandExecutorBase} from './CommandExecutor.base.js';
2 | import {CommandParameters} from './CommandParameters.js';
3 | import {CommandType} from './CommandType.js';
4 |
5 | /**
6 | * Browser implementation of CommandExecutor without file system support
7 | */
8 | export class CommandExecutor extends CommandExecutorBase {
9 | /**
10 | * Execute the associate schema command (not supported in browser)
11 | */
12 | protected async executeAssociateSchema(_commandArgs: CommandParameters[CommandType.ASSOCIATE_SCHEMA]): Promise {
13 | const errorMessage = 'Associate Schema command is not supported in browser environments (requires file system access)';
14 | this.connection.window.showErrorMessage(errorMessage);
15 |
16 | return {
17 | success: false,
18 | message: errorMessage
19 | };
20 | }
21 |
22 | /**
23 | * Execute the remove schema command (not supported in browser)
24 | */
25 | protected async executeRemoveSchema(_commandArgs: CommandParameters[CommandType.REMOVE_SCHEMA]): Promise {
26 | const errorMessage = 'Remove Schema command is not supported in browser environments (requires file system access)';
27 | this.connection.window.showErrorMessage(errorMessage);
28 |
29 | return {
30 | success: false,
31 | message: errorMessage
32 | };
33 | }
34 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/KsonCoreTestStringError.kt:
--------------------------------------------------------------------------------
1 | package org.kson
2 |
3 | import org.kson.parser.messages.MessageType.*
4 | import kotlin.test.Test
5 |
6 | class KsonCoreTestStringError : KsonCoreTestError {
7 | @Test
8 | fun testBadStringEscape() {
9 | assertParserRejectsSource("'this has \\x which is an illegal escape'", listOf(STRING_BAD_ESCAPE))
10 | }
11 |
12 | @Test
13 | fun testBadUnicodeEscape() {
14 | assertParserRejectsSource("'\\u12'", listOf(STRING_BAD_UNICODE_ESCAPE))
15 | assertParserRejectsSource("'\\u12x9'", listOf(STRING_BAD_UNICODE_ESCAPE))
16 | assertParserRejectsSource("'\\u'", listOf(STRING_BAD_UNICODE_ESCAPE))
17 | assertParserRejectsSource("'\\u", listOf(STRING_NO_CLOSE, STRING_BAD_UNICODE_ESCAPE))
18 | }
19 |
20 | @Test
21 | fun testDanglingEscapes() {
22 | assertParserRejectsSource("'\\'", listOf(STRING_NO_CLOSE))
23 | assertParserRejectsSource("'\\", listOf(STRING_NO_CLOSE, STRING_BAD_ESCAPE))
24 | }
25 |
26 | @Test
27 | fun testUnclosedStringError() {
28 | assertParserRejectsSource("\"unclosed", listOf(STRING_NO_CLOSE))
29 | }
30 |
31 | @Test
32 | fun testTwoConsecutiveStrings() {
33 | assertParserRejectsSource("'a string''an illegal second string'", listOf(EOF_NOT_REACHED))
34 | }
35 |
36 | @Test
37 | fun testStringWithNullByte() {
38 | assertParserRejectsSource("my_bad_string: 'a a' ", listOf(STRING_CONTROL_CHARACTER))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/README.md:
--------------------------------------------------------------------------------
1 | # Kson Language Server
2 |
3 | A Language Server Protocol (LSP) implementation for Kson, written in TypeScript. We support the
4 | [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/), because it is a standard that allows programming language tooling to be decoupled
5 | from the code editor.
6 |
7 | By implementing a language server, this project provides Kson language support that can be used by any LSP-compatible
8 | editor, such as Visual Studio Code, Neovim, or Sublime Text. This approach avoids the need to write a new extension for
9 | each editor and ensures that features are implemented in one place, improving performance and maintainability [1].
10 |
11 | ## Current Features
12 |
13 | * **Real-time Diagnostics:** Identifies syntax errors as you type.
14 | * **Document Formatting:** Automatically formats Kson files.
15 | * **Semantic Highlighting:** Provides rich, context-aware syntax highlighting.
16 |
17 | ## Getting Started
18 |
19 | ### Prerequisites
20 |
21 | * Node.js (v20.0.0 or higher)
22 |
23 | ### Installation
24 |
25 | ```bash
26 | npm install
27 | ```
28 |
29 | ### Build
30 |
31 | To compile the TypeScript source code, run:
32 |
33 | ```bash
34 | npm run build
35 | ```
36 |
37 | ### Testing
38 |
39 | To run the test suite:
40 |
41 | ```bash
42 | npm test
43 | ```
44 |
45 | [1] Visual Studio Code. (2025). *Language Server Extension Guide*. Retrieved
46 | from https://code.visualstudio.com/api/language-extensions/language-server-extension-guide
--------------------------------------------------------------------------------
/tooling/language-server-protocol/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kson-language-server",
3 | "authors": [
4 | {
5 | "name": "Bart Dubbeldam",
6 | "email": "bart.dubbeldam@dc-analytic.com"
7 | },
8 | {
9 | "name": "Daniel Marcotte",
10 | "email": "dmarcotte@gmail.com"
11 | }
12 | ],
13 | "version": "0.0.1",
14 | "description": "Kson Language Server implementation in TypeScript",
15 | "type": "module",
16 | "exports": {
17 | ".": {
18 | "types": "./out/index.d.ts",
19 | "default": "./out/index.js"
20 | },
21 | "./node": {
22 | "types": "./out/index.node.d.ts",
23 | "default": "./out/index.node.js"
24 | },
25 | "./browser": {
26 | "types": "./out/index.browser.d.ts",
27 | "default": "./out/index.browser.js"
28 | }
29 | },
30 | "scripts": {
31 | "compile": "tsc",
32 | "test": "mocha 'out/test/**/*.test.js'"
33 | },
34 | "keywords": [
35 | "kson",
36 | "language-server",
37 | "lsp",
38 | "typescript"
39 | ],
40 | "engines": {
41 | "node": ">=20.0.0"
42 | },
43 | "dependencies": {
44 | "kson": "file:../../kson-lib/build/dist/js/productionLibrary",
45 | "kson-tooling": "file:../../kson-tooling-lib/build/dist/js/productionLibrary",
46 | "vscode-languageserver": "^9.0.1",
47 | "vscode-languageserver-textdocument": "^1.0.12",
48 | "vscode-uri": "^3.0.8"
49 | },
50 | "devDependencies": {
51 | "@types/mocha": "10.0.10",
52 | "@types/node": "20.0.0",
53 | "mocha": "^10.0.10",
54 | "tsx": "^4.20.2",
55 | "typescript": "^5.8.3"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/formatter/KsonExternalFormatter.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.formatter
2 |
3 | import com.intellij.formatting.FormattingContext
4 | import com.intellij.formatting.service.AbstractDocumentFormattingService
5 | import com.intellij.formatting.service.FormattingService.Feature
6 | import com.intellij.openapi.editor.Document
7 | import com.intellij.openapi.util.TextRange
8 | import com.intellij.psi.PsiFile
9 | import org.kson.jetbrains.psi.KsonPsiFile
10 | import org.kson.tools.IndentType
11 | import org.kson.tools.KsonFormatterConfig
12 | import org.kson.tools.format
13 |
14 | class KsonExternalFormatter : AbstractDocumentFormattingService() {
15 | override fun canFormat(file: PsiFile): Boolean {
16 | return file is KsonPsiFile
17 | }
18 |
19 | override fun getFeatures(): Set = emptySet()
20 |
21 | override fun formatDocument(
22 | document: Document,
23 | formattingRanges: MutableList,
24 | formattingContext: FormattingContext,
25 | canChangeWhiteSpaceOnly: Boolean,
26 | quickFormat: Boolean
27 | ) {
28 | val indentOptions = formattingContext.codeStyleSettings
29 | .getIndentOptions(formattingContext.containingFile.fileType)
30 | val indentType = if (indentOptions.USE_TAB_CHARACTER) {
31 | IndentType.Tab()
32 | } else {
33 | IndentType.Space(indentOptions.INDENT_SIZE)
34 | }
35 |
36 | val formatted = format(document.text, KsonFormatterConfig(indentType))
37 | document.setText(formatted)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/psi/KsonElementGeneratorTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.psi
2 |
3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase
4 | import org.kson.parser.behavior.embedblock.EmbedDelim
5 |
6 | class KsonElementGeneratorTest : BasePlatformTestCase() {
7 | private lateinit var generator: KsonElementGenerator
8 |
9 | override fun setUp() {
10 | super.setUp()
11 | generator = KsonElementGenerator(project)
12 | }
13 |
14 | fun testEmbedBlockGeneration() {
15 | val content = """
16 | | content with a newline current indent of 2
17 | |
18 | """.trimMargin()
19 | val generatedBlock = generator.createEmbedBlock(EmbedDelim.Percent, content, tag = "custom")
20 |
21 | val expectedBlock = """
22 | |%custom
23 | | content with a newline current indent of 2
24 | | %%
25 | """.trimMargin()
26 | assertEquals(generatedBlock.text, expectedBlock)
27 | }
28 |
29 | fun testEmbedBlockGenerationWithIndent() {
30 | val content = """
31 | | content with a newline and added indent of 2
32 | |
33 | """.trimMargin()
34 | val generatedBlock = generator.createEmbedBlock(EmbedDelim.Percent, content, tag = "custom", indentText = " ")
35 |
36 | val expectedBlock = """
37 | |%custom
38 | | content with a newline and added indent of 2
39 | | %%
40 | """.trimMargin()
41 | assertEquals(generatedBlock.text, expectedBlock)
42 | }
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/examples/schemas/config.schema.kson:
--------------------------------------------------------------------------------
1 | '$schema': 'http://json-schema.org/draft-07/schema#'
2 | type: object
3 | title: 'Application Configuration'
4 | description: 'Configuration schema for the application'
5 | properties:
6 | appName:
7 | type: string
8 | description: 'The name of the application'
9 | title: 'Application Name'
10 | .
11 | version:
12 | type: string
13 | description: 'The version number'
14 | title: Version
15 | pattern: '^\\d+\\.\\d+\\.\\d+$'
16 | .
17 | server:
18 | type: object
19 | description: 'Server configuration'
20 | title: 'Server Settings'
21 | properties:
22 | host:
23 | type: string
24 | description: 'The server host address'
25 | title: Host
26 | default: localhost
27 | .
28 | port:
29 | type: number
30 | description: 'The port number the server listens on'
31 | title: 'Port Number'
32 | minimum: 1024
33 | maximum: 65535
34 | default: 8080
35 | .
36 | enabled:
37 | type: boolean
38 | description: 'Whether the server is enabled'
39 | title: 'Enabled Flag'
40 | default: true
41 | .
42 | .
43 | required:
44 | - host
45 | - port
46 | .
47 | features:
48 | type: array
49 | description: 'List of enabled features'
50 | title: Features
51 | items:
52 | type: string
53 | enum:
54 | - logging
55 | - metrics
56 | - tracing
57 | - caching
58 | .
59 | .
60 | .
61 | required:
62 | - appName
63 | - version
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import importMetaUrlPlugin from '@codingame/esbuild-import-meta-url-plugin';
3 | import { resolve } from 'path';
4 |
5 | export default defineConfig(({ }) => {
6 | return {
7 | base: './',
8 | optimizeDeps: {
9 | esbuildOptions: {
10 | plugins: [
11 | importMetaUrlPlugin
12 | ]
13 | },
14 | include: [
15 | 'monaco-editor-wrapper',
16 | 'vscode-languageclient',
17 | 'vscode-textmate',
18 | '@codingame/monaco-vscode-textmate-service-override',
19 | 'vscode-oniguruma'
20 | ]
21 | },
22 | worker: {
23 | format: 'es'
24 | },
25 | build: {
26 | target: 'esnext',
27 | lib: {
28 | entry: resolve(__dirname, 'src/index.ts'),
29 | fileName: 'kson-monaco',
30 | formats: ['es']
31 | },
32 | rollupOptions: {
33 | output: {
34 | // Preserve named exports
35 | preserveModules: false,
36 | // Use a single chunk to ensure exports are accessible
37 | inlineDynamicImports: false
38 | }
39 | },
40 | // Enable code splitting for better lazy loading
41 | minify: 'esbuild',
42 | sourcemap: true,
43 | },
44 | server: {
45 | port: 5173,
46 | open: true
47 | }
48 | };
49 | });
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/CommandParameters.ts:
--------------------------------------------------------------------------------
1 | import { CommandType } from './CommandType.js';
2 | import { Command } from 'vscode-languageserver';
3 | import { FormattingStyle } from 'kson';
4 |
5 | /**
6 | * Type-safe mapping of command types to their expected parameter structures
7 | */
8 | export interface CommandParameters {
9 | [CommandType.PLAIN_FORMAT ]: {
10 | documentUri: string;
11 | formattingStyle: FormattingStyle;
12 | };
13 | [CommandType.COMPACT_FORMAT ]: {
14 | documentUri: string;
15 | formattingStyle: FormattingStyle;
16 | };
17 | [CommandType.DELIMITED_FORMAT]: {
18 | documentUri: string;
19 | formattingStyle: FormattingStyle;
20 | };
21 | [CommandType.CLASSIC_FORMAT]: {
22 | documentUri: string;
23 | formattingStyle: FormattingStyle;
24 | };
25 | [CommandType.ASSOCIATE_SCHEMA]: {
26 | documentUri: string;
27 | schemaPath: string;
28 | };
29 | [CommandType.REMOVE_SCHEMA]: {
30 | documentUri: string;
31 | };
32 | }
33 |
34 | /**
35 | * Type guard to check if a string is a valid CommandType
36 | */
37 | export function isValidCommand(command: string): command is CommandType {
38 | return Object.values(CommandType).includes(command as CommandType);
39 | }
40 |
41 | /**
42 | * Helper function to create a type-safe command
43 | */
44 | export function createTypedCommand(
45 | type: T,
46 | title: string,
47 | params: CommandParameters[T]
48 | ): Command {
49 | return {
50 | title,
51 | command: type,
52 | arguments: [params]
53 | };
54 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/schema/validators/TypeValidator.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.parser.MessageSink
4 | import org.kson.parser.messages.MessageType
5 | import org.kson.schema.asSchemaInteger
6 | import org.kson.value.*
7 |
8 | /**
9 | * Json Schema `type:` validator
10 | */
11 | class TypeValidator(private val allowedTypes: List) {
12 | constructor(type: String) : this(listOf(type))
13 |
14 | /**
15 | * Validates whether the given [ksonValue] is valid according to this [TypeValidator].
16 | * If [ksonValue] is invalid, validation errors are written to [messageSink] and this method returns false
17 | *
18 | * @return true if [ksonValue] is valid, [false otherwise]
19 | */
20 | fun validate(ksonValue: KsonValue, messageSink: MessageSink): Boolean {
21 | val nodeType = when (ksonValue) {
22 | is KsonBoolean -> "boolean"
23 | is KsonNull -> "null"
24 | is KsonNumber -> {
25 | if (asSchemaInteger(ksonValue) != null) {
26 | "integer"
27 | } else {
28 | "number"
29 | }
30 | }
31 | is KsonString -> "string"
32 | is KsonList -> "array"
33 | is KsonObject -> "object"
34 | is EmbedBlock -> "object"
35 | }
36 |
37 | if (!allowedTypes.contains(nodeType)
38 | // if our node is an integer, this type is valid if the more-general "number" is an allowedType
39 | && !(nodeType == "integer" && allowedTypes.contains("number"))) {
40 | messageSink.error(ksonValue.location, MessageType.SCHEMA_VALUE_TYPE_MISMATCH.create(allowedTypes.joinToString(), nodeType))
41 | return false
42 | }
43 |
44 | return true
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/inject/KsonLanguageInjectionContributor.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.inject
2 |
3 | import com.intellij.lang.Language
4 | import com.intellij.lang.LanguageUtil
5 | import com.intellij.lang.injection.general.Injection
6 | import com.intellij.lang.injection.general.LanguageInjectionContributor
7 | import com.intellij.lang.injection.general.SimpleInjection
8 | import com.intellij.psi.PsiElement
9 | import org.kson.jetbrains.psi.KsonEmbedContent
10 |
11 | /**
12 | * Language injection contributor for KSON embed content blocks.
13 | * In this class an injection is 'prepared' that is performed by the [KsonLanguageInjectionPerformer]
14 | */
15 | class KsonLanguageInjectionContributor : LanguageInjectionContributor {
16 |
17 | override fun getInjection(context: PsiElement): Injection? {
18 | // Only inject into KsonEmbedContent elements
19 | if (context !is KsonEmbedContent || !context.isValidHost) {
20 | return null
21 | }
22 |
23 | val embedBlock = context.embedBlock ?: return null
24 | val languageTag = embedBlock.embedBlockTag ?: return null
25 |
26 | // Find the language by tag
27 | val language = findLanguageForInjection(languageTag) ?: return null
28 |
29 | return SimpleInjection(language, "", "", null)
30 | }
31 |
32 | private fun findLanguageForInjection(languageTag: String): Language? {
33 | val registeredLanguages = Language.getRegisteredLanguages()
34 | val language = registeredLanguages.find { it.id.equals(languageTag, ignoreCase = true) }
35 | return language?.takeIf { LanguageUtil.isInjectableLanguage(it) }
36 | }
37 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/inject/KsonLanguageInjectionPerformer.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.inject
2 |
3 | import com.intellij.lang.injection.general.Injection
4 | import com.intellij.lang.injection.general.LanguageInjectionPerformer
5 | import com.intellij.lang.injection.MultiHostRegistrar
6 | import com.intellij.psi.PsiElement
7 | import org.kson.jetbrains.psi.KsonEmbedContent
8 |
9 | /**
10 | * Language injection performer for KSON embed content blocks.
11 | * Handles the actual injection mechanics using the existing robust infrastructure.
12 | */
13 | class KsonLanguageInjectionPerformer : LanguageInjectionPerformer {
14 | override fun isPrimary(): Boolean {
15 | return true
16 | }
17 |
18 | override fun performInjection(registrar: MultiHostRegistrar, injection: Injection, context: PsiElement): Boolean {
19 | if (context !is KsonEmbedContent || !context.isValidHost) {
20 | return false
21 | }
22 |
23 | // Get the ranges that will be used for injection using existing infrastructure
24 | val ranges = context.indentHandler.getUntrimmedRanges(context)
25 | if (ranges.isEmpty()) return false
26 |
27 | val language = injection.injectedLanguage ?: return false
28 | registrar.startInjecting(language)
29 |
30 | // Process each range individually to maintain proper mapping
31 | for (range in ranges) {
32 | registrar.addPlace(
33 | injection.prefix,
34 | injection.suffix,
35 | context,
36 | range
37 | )
38 | }
39 |
40 | registrar.doneInjecting()
41 | return true
42 | }
43 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/readme.md:
--------------------------------------------------------------------------------
1 | # [Kson](https://kson.org) support for [JetBrains IDEs](https://www.jetbrains.com/products/#type=ide)
2 |
3 | This [Gradle subproject](https://docs.gradle.org/current/userguide/multi_project_builds.html) implements [Kson](https://kson.org) support for [JetBrains IDEs](https://www.jetbrains.com/products/#type=ide)
4 |
5 | ### Development
6 |
7 | This is a subproject of the main [Kson](../../readme.md) implementation, and so requires no special setup.
8 |
9 | **NOTE: all subproject Gradle commands are run from the [project root](../..)**.
10 |
11 | Some useful Gradle commands for this subproject:
12 |
13 | ```bash
14 | # Run Intellij IDEA with the development plugin installed
15 | ./gradlew :tooling:jetbrains:runIde
16 |
17 | # Run the plugin tests
18 | ./gradlew :tooling:jetbrains:test
19 |
20 | # Verify plugin compatibility
21 | # (https://plugins.jetbrains.com/docs/intellij/api-changes-list.html#verifying-compatibility)
22 | ./gradlew :tooling:jetbrains:runPluginVerifier
23 | ```
24 |
25 | ### Intellij Platform Upgrades and Maintenance
26 |
27 | This (sub)project is built from a stripped-down version of the [Intellij Platform Plugin Template](https://github.com/JetBrains/intellij-platform-plugin-template), so when upgrading to integrate with newer versions of the Intellij Platform, we should consult that project for updates and documentation on the latest plugin integration/API guidance
28 |
29 | ### Running and Debugging
30 |
31 | Once your [Kson](../../readme.md) project is loaded into intellij as described in [**Development setup**](https://github.com/kson-org/kson#development-setup), the `tooling:jetbrains:runIde` Gradle task can be run and debugged from the Gradle pane within Intellij
32 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/suite/syntax-highlighting.test.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { assert } from './assert';
3 | import { createTestFile, cleanUp } from './common';
4 | import TextmateLanguageService from 'vscode-textmate-languageservice';
5 |
6 | describe('Syntax Highlighting Tests', () => {
7 | let testFileUri: vscode.Uri | undefined;
8 |
9 | afterEach(async () => {
10 | if (testFileUri) {
11 | await cleanUp(testFileUri);
12 | testFileUri = undefined;
13 | }
14 | });
15 |
16 | async function getTokenScopesAtPosition(document: vscode.TextDocument, line: number, character: number): Promise {
17 | const position = new vscode.Position(line, character);
18 | const tokenInfo = await TextmateLanguageService.api.getScopeInformationAtPosition(document, position);
19 | console.log(`Token at line ${line}, char ${character}:`, tokenInfo);
20 | return tokenInfo.scopes;
21 | }
22 |
23 | it('Should highlight Python embedded blocks', async () => {
24 | const content = `key: %python
25 | print("Hello, World!")
26 | def greet(name):
27 | return f"Hello, {name}!"
28 | %%`;
29 |
30 | const [uri, document] = await createTestFile(content);
31 | testFileUri = uri;
32 |
33 | // Check that Python code is tagged as python
34 | const pythonScopes = await getTokenScopesAtPosition(document, 1, 10);
35 | console.log("Python code scopes:", pythonScopes);
36 | assert.ok(pythonScopes.some(scope => scope.includes('source.python') || scope.includes('meta.embedded.python')));
37 | }).timeout(10000);
38 | });
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/parser/behavior/embedblock/EmbedBlockSerialization.kt:
--------------------------------------------------------------------------------
1 | package org.kson.parser.behavior.embedblock
2 |
3 | import org.kson.ast.StringNodeImpl
4 | import org.kson.ast.EmbedBlockNode
5 |
6 | /**
7 | * An Embed Block is equivalent to an object with a string property named [EmbedObjectKeys.EMBED_CONTENT] and NO
8 | * other properties except (optionally) [EmbedObjectKeys.EMBED_TAG] or [EmbedObjectKeys.EMBED_METADATA]. Objects
9 | * of this shape can be serialized to and from our Embed Block syntax without any loss or corruption of the data.
10 | * The embed block syntax may be considered a "view" on objects of this shape.
11 | */
12 | enum class EmbedObjectKeys(val key: String) {
13 | EMBED_TAG("embedTag"),
14 | EMBED_METADATA("embedMetadata"),
15 | EMBED_CONTENT("embedContent");
16 |
17 | companion object {
18 | /**
19 | * Check whether the given [properties] can be decoded to an [EmbedBlockNode].
20 | * This is the case when:
21 | * 1. All values of [properties] are [StringNodeImpl]'s.
22 | * 2. [properties] contains an [EMBED_CONTENT] key
23 | * 3. [properties] contains no other keys than [EmbedObjectKeys.entries]
24 | */
25 | fun canBeDecoded(properties: Map): Boolean{
26 | val allStrings = properties.all { it.value != null }
27 | val containsContent = properties.containsKey(EMBED_CONTENT.key)
28 | val hasOnlyEmbedObjectKeys = properties.keys.all { key ->
29 | EmbedObjectKeys.entries.any { it.key == key }
30 | }
31 | return !(!allStrings || !containsContent || !hasOnlyEmbedObjectKeys)
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/index.common.ts:
--------------------------------------------------------------------------------
1 | // Common test runner logic shared between Node and browser environments
2 | import testManifest from './test-files.json';
3 |
4 | export async function runTests(mocha: any, isBrowser: boolean): Promise {
5 | return new Promise((resolve, reject) => {
6 | // Load tests
7 | const loadTests = async () => {
8 | if (isBrowser) {
9 | // Browser: use dynamic imports from manifest
10 | for (const testName of testManifest.tests) {
11 | await import(`./suite/${testName}.ts`);
12 | }
13 | } else {
14 | // Node: use addFile
15 | const path = await import('path');
16 | const testsRoot = path.resolve(__dirname);
17 |
18 | // Use test files from manifest
19 | testManifest.tests.forEach(testName => {
20 | mocha.addFile(path.resolve(testsRoot, 'suite', `${testName}.js`));
21 | });
22 | }
23 | };
24 |
25 | // Run tests
26 | loadTests().then(() => {
27 | try {
28 | mocha.run((failures: number) => {
29 | if (failures > 0) {
30 | reject(new Error(`${failures} tests failed.`));
31 | } else {
32 | resolve();
33 | }
34 | });
35 | } catch (err) {
36 | console.error(err);
37 | reject(err);
38 | }
39 | }).catch(err => {
40 | console.error('Failed to load tests:', err);
41 | reject(err);
42 | });
43 | });
44 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | base
3 | }
4 |
5 | tasks {
6 | val npmInstall = register("npmInstall") {
7 | command=listOf("npm", "install")
8 | doNotTrackState("npm already tracks its own state")
9 | dependsOn(":tooling:language-server-protocol:npm_run_test")
10 | }
11 |
12 | register("npm_run_vscode") {
13 | command=listOf("npm", "run", "vscode")
14 | dependsOn(npmInstall)
15 | }
16 |
17 | register("npm_run_monaco") {
18 | command=listOf("npm", "run", "monaco")
19 | dependsOn(npmInstall)
20 | }
21 |
22 | val test = register("npm_run_test") {
23 | command=listOf("npm", "run", "test")
24 | dependsOn(npmInstall)
25 | }
26 |
27 | val buildVsCode = register("npm_run_buildVSCode") {
28 | command=listOf("npm", "run", "buildVSCode")
29 | dependsOn(npmInstall)
30 | }
31 |
32 | val buildMonaco = register("npm_run_buildMonaco") {
33 | command=listOf("npm", "run", "buildMonaco")
34 | dependsOn(npmInstall)
35 | }
36 |
37 | check {
38 | dependsOn(test)
39 | /**
40 | * TODO - Ideally this task is "npm_run_buildPlugins" building both plugins, however, for now the Monaco Vite build
41 | * is too unpredictable in CI
42 | */
43 | dependsOn(buildVsCode)
44 | }
45 |
46 | clean {
47 | delete("node_modules")
48 | delete("vscode/out")
49 | delete("vscode/dist")
50 | delete("vscode/node_modules")
51 | delete("monaco/dist")
52 | delete("monaco/node_modules")
53 | delete("shared/out")
54 | }
55 | }
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/features/HoverService.ts:
--------------------------------------------------------------------------------
1 | import {Hover, Position, MarkupKind} from 'vscode-languageserver';
2 | import {KsonDocument} from '../document/KsonDocument.js';
3 | import {KsonTooling} from 'kson-tooling';
4 |
5 | /**
6 | * Service for providing hover information based on JSON Schema.
7 | */
8 | export class HoverService {
9 |
10 | /**
11 | * Get hover information for a position in a document.
12 | *
13 | * @param document The document to get hover info for
14 | * @param position The position in the document
15 | * @returns Hover information, or null if none available
16 | */
17 | getHover(document: KsonDocument, position: Position): Hover | null {
18 | // Get the schema for this document
19 | const schemaDocument = document.getSchemaDocument();
20 | if (!schemaDocument) {
21 | // No schema configured, no hover info available
22 | return null;
23 | }
24 |
25 | // Call the KsonTooling API to get schema hover info at this position
26 | // The API now accepts line and column directly, avoiding the Location mangling issue
27 | const tooling = KsonTooling.getInstance();
28 | const hoverMarkdown = tooling.getSchemaInfoAtLocation(
29 | document.getText(),
30 | schemaDocument.getText(),
31 | position.line,
32 | position.character
33 | );
34 |
35 | if (!hoverMarkdown) {
36 | return null;
37 | }
38 |
39 | // Return LSP Hover response with markdown content
40 | return {
41 | contents: {
42 | kind: MarkupKind.Markdown,
43 | value: hoverMarkdown
44 | }
45 | };
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/schema/SchemaConfig.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Configuration for mapping KSON files to their JSON schemas.
3 | */
4 |
5 | /**
6 | * A single schema mapping that associates file patterns with a schema.
7 | */
8 | export interface SchemaMapping {
9 | /**
10 | * Glob patterns to match file paths.
11 | * Examples: ["config/*.kson"] or glob patterns with wildcards
12 | */
13 | fileMatch: string[];
14 |
15 | /**
16 | * Workspace-relative path to the schema file.
17 | * Example: "schemas/config.schema.json"
18 | */
19 | schema: string;
20 | }
21 |
22 | /**
23 | * The root configuration object for .kson-schema.json
24 | */
25 | export interface SchemaConfig {
26 | /**
27 | * Array of schema mappings.
28 | */
29 | schemas: SchemaMapping[];
30 | }
31 |
32 | /**
33 | * Type guard to check if an object is a valid SchemaConfig
34 | */
35 | export function isValidSchemaConfig(obj: unknown): obj is SchemaConfig {
36 | if (typeof obj !== 'object' || obj === null) {
37 | return false;
38 | }
39 |
40 | const config = obj as Record;
41 |
42 | if (!Array.isArray(config.schemas)) {
43 | return false;
44 | }
45 |
46 | return config.schemas.every((mapping: unknown) => {
47 | if (typeof mapping !== 'object' || mapping === null) {
48 | return false;
49 | }
50 |
51 | const m = mapping as Record;
52 |
53 | return (
54 | Array.isArray(m.fileMatch) &&
55 | m.fileMatch.every((pattern: unknown) => typeof pattern === 'string') &&
56 | typeof m.schema === 'string'
57 | );
58 | });
59 | }
60 |
61 | export const SCHEMA_CONFIG_FILENAME = '.kson-schema.kson';
62 |
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/parser/KsonLexerTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.parser
2 |
3 | import com.intellij.testFramework.LightPlatformTestCase
4 | import org.kson.parser.TokenType
5 |
6 | class KsonLexerTest : LightPlatformTestCase() {
7 |
8 | fun testLexerWithNonZeroStartOffset() {
9 | val fullBuffer = """prefix content {key:value}"""
10 | val lexer = KsonLexer()
11 |
12 | // Start lexing from offset 15 (after "prefix content ")
13 | val startOffset = 15
14 | val endOffset = fullBuffer.length
15 | lexer.start(fullBuffer, startOffset, endOffset, 0)
16 |
17 | // {
18 | assertEquals(elem(TokenType.CURLY_BRACE_L), lexer.tokenType)
19 | // Token start should be at position 15 (not 0)
20 | assertEquals(15, lexer.tokenStart)
21 | assertEquals(16, lexer.tokenEnd)
22 |
23 | // key
24 | lexer.advance()
25 | assertEquals(elem(TokenType.UNQUOTED_STRING), lexer.tokenType)
26 | // Token positions should be relative to the full buffer
27 | assertEquals(16, lexer.tokenStart)
28 | assertEquals(19, lexer.tokenEnd)
29 |
30 | // :
31 | lexer.advance()
32 | assertEquals(elem(TokenType.COLON), lexer.tokenType)
33 | assertEquals(19, lexer.tokenStart)
34 | assertEquals(20, lexer.tokenEnd)
35 |
36 | // value
37 | lexer.advance()
38 | assertEquals(elem(TokenType.UNQUOTED_STRING), lexer.tokenType)
39 | assertEquals(20, lexer.tokenStart)
40 | assertEquals(25, lexer.tokenEnd)
41 |
42 | // }
43 | lexer.advance()
44 | assertEquals(elem(TokenType.CURLY_BRACE_R), lexer.tokenType)
45 | assertEquals(25, lexer.tokenStart)
46 | assertEquals(26, lexer.tokenEnd)
47 | }
48 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/suite/common.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { v4 as uuid } from 'uuid';
3 | import { assert } from './assert';
4 |
5 | export async function createTestFile(initialContent: string = '', fileName: string = `${uuid()}.kson`): Promise<[vscode.Uri, vscode.TextDocument]> {
6 | const workspaceFolder = getWorkspaceFolder();
7 |
8 | // Always use VS Code's URI joining which works in both environments
9 | const uri = vscode.Uri.joinPath(workspaceFolder.uri, fileName);
10 |
11 | // Use TextEncoder for browser compatibility (works in Node.js too)
12 | const encoder = new TextEncoder();
13 | await vscode.workspace.fs.writeFile(uri, encoder.encode(initialContent));
14 |
15 | const document = await vscode.workspace.openTextDocument(uri);
16 | await vscode.window.showTextDocument(document);
17 |
18 | return [uri, document];
19 | }
20 |
21 | export async function cleanUp(uri: vscode.Uri) {
22 | if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri.toString() === uri.toString()) {
23 | await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
24 | }
25 | await vscode.workspace.fs.delete(uri, { useTrash: false });
26 | }
27 |
28 | export function assertTextEqual(document: vscode.TextDocument, expectedText: string) {
29 | // Note: we transform `\r\n` into `\n` for cross-platform compatibility
30 | const actualText = document.getText().replace(/\r\n/g, '\n');
31 | assert.strictEqual(actualText, expectedText);
32 | }
33 |
34 | function getWorkspaceFolder(): vscode.WorkspaceFolder {
35 | const workspaceFolders = vscode.workspace.workspaceFolders;
36 | if (!workspaceFolders || workspaceFolders.length === 0) {
37 | throw new Error('No workspace folder open');
38 | }
39 | return workspaceFolders[0];
40 | }
--------------------------------------------------------------------------------
/jdk.properties:
--------------------------------------------------------------------------------
1 | ##
2 | # Declare the desired JDK version for our build. We use the `me.filippov.gradle.jvm.wrapper` Gradle plugin
3 | # to include this JDK in our Gradle wrapper, making this our truly "built-in" JDK for a trivial and robust
4 | # developer setup
5 | JDK_VERSION=21.0.2
6 |
7 | ##
8 | # Cross-platform download URLs for the version of the JDK we are currently using, used by the
9 | # `me.filippov.gradle.jvm.wrapper` Gradle plugin
10 | #
11 | # Source: https://adoptium.net/temurin/releases/
12 | #
13 | # The JDK specified here is installed automatically on invocations of `./gradlew`
14 | # and is used by Gradle itself to run, which then uses it as the default JDK `toolchain`
15 | # for the entire build.
16 | #
17 | # NOTE: these URLs must point to a JDK that matches the declared JDK_VERSION above, else
18 | # the BAD_JDK_VERSION_ERROR will be triggered
19 | #
20 | # IF THESE URLs CHANGE: `./gradlew wrapper` must be run after any edits for them to take effect,
21 | # and `./gradlew check` should also be run to ensure the new JDK is loaded and verified
22 | unixJvmInstallDir=$APP_HOME/gradle/jdk
23 | winJvmInstallDir=%APP_HOME%\\gradle\\jdk
24 | macAarch64JvmUrl=https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_macos-aarch64_bin.tar.gz
25 | macX64JvmUrl=https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_macos-x64_bin.tar.gz
26 | linuxAarch64JvmUrl = https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-aarch64_bin.tar.gz
27 | linuxX64JvmUrl = https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz
28 | windowsX64JvmUrl = https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_windows-x64_bin.zip
29 |
30 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/vscode/test/suite/editing.test.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { createTestFile, cleanUp, assertTextEqual } from './common';
3 |
4 |
5 | describe('Editing Tests', () => {
6 | let testFileUri: vscode.Uri | undefined;
7 |
8 | afterEach(async () => {
9 | if (testFileUri) {
10 | await cleanUp(testFileUri);
11 | testFileUri = undefined;
12 | }
13 | });
14 |
15 | it('Should auto-close $ to $$$', async () => {
16 | const [uri, document] = await createTestFile();
17 | testFileUri = uri;
18 |
19 | await vscode.commands.executeCommand('type', { text: '$' });
20 |
21 | assertTextEqual(document, '$$$');
22 | }).timeout(10000);
23 |
24 | it('Should auto-close % to %%%', async () => {
25 | const [uri, document] = await createTestFile();
26 | testFileUri = uri;
27 |
28 | await vscode.commands.executeCommand('type', { text: '%' });
29 |
30 | assertTextEqual(document, '%%%');
31 | }).timeout(10000);
32 |
33 | it('Should indent an embed block delimited with $, without tag', async () => {
34 | const [uri, document] = await createTestFile();
35 | testFileUri = uri;
36 |
37 | await vscode.commands.executeCommand('type', { text: 'key: $\n' });
38 |
39 | assertTextEqual(document, [
40 | 'key: $',
41 | ' $$'
42 | ].join('\n'));
43 | }).timeout(10000);
44 |
45 | it('Should indent an embed block delimited with %, with tag', async () => {
46 | const [uri, document] = await createTestFile();
47 | testFileUri = uri;
48 |
49 | await vscode.commands.executeCommand('type', { text: 'key: %tag\n' });
50 |
51 | assertTextEqual(document, [
52 | 'key: %tag',
53 | ' %%'
54 | ].join('\n'));
55 | }).timeout(10000);
56 | });
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/KsonCoreTestNumber.kt:
--------------------------------------------------------------------------------
1 | package org.kson
2 |
3 | import kotlin.test.Test
4 |
5 | class KsonCoreTestNumber : KsonCoreTest {
6 | /**
7 | * See also [org.kson.parser.NumberParserTest] for more targeted number parsing tests
8 | */
9 | @Test
10 | fun testNumberLiteralSource() {
11 | assertParsesTo("42", "42", "42", "42")
12 | assertParsesTo("042", "42", "42", "42")
13 | assertParsesTo("42.1", "42.1", "42.1", "42.1")
14 | assertParsesTo("00042.1", "42.1", "42.1", "42.1")
15 | assertParsesTo("42.1E0", "42.1e0", "42.1e0", "42.1e0")
16 | assertParsesTo("42.1e0", "42.1e0", "42.1e0", "42.1e0")
17 | assertParsesTo("4.21E1", "4.21e1", "4.21e1", "4.21e1")
18 | assertParsesTo("421E-1", "421e-1", "421e-1", "421e-1")
19 | assertParsesTo("4210e-2", "4210e-2", "4210e-2", "4210e-2")
20 | assertParsesTo("0.421e2", "0.421e2", "0.421e2", "0.421e2")
21 | assertParsesTo("0.421e+2", "0.421e+2", "0.421e+2", "0.421e+2")
22 | assertParsesTo("42.1E+0", "42.1e+0", "42.1e+0", "42.1e+0")
23 | assertParsesTo("00042.1E0", "42.1e0", "42.1e0", "42.1e0")
24 | assertParsesTo("-42.1", "-42.1", "-42.1", "-42.1")
25 | assertParsesTo("-42.1E0", "-42.1e0", "-42.1e0", "-42.1e0")
26 | assertParsesTo("-42.1e0", "-42.1e0", "-42.1e0", "-42.1e0")
27 | assertParsesTo("-4.21E1", "-4.21e1", "-4.21e1", "-4.21e1")
28 | assertParsesTo("-421E-1", "-421e-1", "-421e-1", "-421e-1")
29 | assertParsesTo("-4210e-2", "-4210e-2", "-4210e-2", "-4210e-2")
30 | assertParsesTo("-0.421e2", "-0.421e2", "-0.421e2", "-0.421e2")
31 | assertParsesTo("-0.421e+2", "-0.421e+2", "-0.421e+2", "-0.421e+2")
32 | assertParsesTo("-42.1E+0", "-42.1e+0", "-42.1e+0", "-42.1e+0")
33 | assertParsesTo("-00042.1E0", "-42.1e0", "-42.1e0", "-42.1e0")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib-python/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "kson-lang"
3 | # [[kson-version-num]]
4 | version = "0.3.0.dev0"
5 | authors = [
6 | { name="KSON", email="kson@kson.org" },
7 | ]
8 | description = "KSON: a next-gen configuration language and love letter to the humans maintaining computer configuration"
9 | readme = "README.md"
10 | requires-python = ">=3.10"
11 | dependencies = [
12 | "cffi>=1.17.1",
13 | ]
14 | classifiers = [
15 | "Development Status :: 4 - Beta",
16 | "Intended Audience :: Developers",
17 | "Topic :: Software Development :: Libraries :: Python Modules",
18 | "Topic :: File Formats :: JSON",
19 | "Topic :: File Formats :: JSON :: JSON Schema",
20 | "Topic :: Text Processing :: Markup",
21 | "Programming Language :: Python :: 3",
22 | "Programming Language :: Python :: 3.10",
23 | "Programming Language :: Python :: 3.11",
24 | "Programming Language :: Python :: 3.12",
25 | "Programming Language :: Python :: 3.13",
26 | "Programming Language :: Python :: Implementation :: CPython",
27 | "Operating System :: MacOS :: MacOS X",
28 | "Operating System :: Microsoft :: Windows",
29 | "Operating System :: POSIX :: Linux",
30 | ]
31 | license = "Apache-2.0"
32 | license-files = ["LICENSE*"]
33 |
34 | [project.urls]
35 | Homepage = "https://github.com/kson-org/kson"
36 | Issues = "https://github.com/kson-org/kson/issues"
37 |
38 |
39 | [build-system]
40 | requires = ["setuptools>=61.0", "wheel"]
41 | build-backend = "build_backend"
42 | backend-path = ["."]
43 |
44 | [tool.setuptools]
45 | packages = ["kson"]
46 | package-dir = {"" = "src"}
47 |
48 | [tool.setuptools.package-data]
49 | kson = ["kson_api.h", "kson.dll", "libkson.dylib", "libkson.so"]
50 |
51 | [dependency-groups]
52 | dev = [
53 | "pyright>=1.1.403",
54 | "pytest>=8.4.1",
55 | "ruff>=0.12.8",
56 | "cibuildwheel>=2.16.0",
57 | "twine>=6.2.0",
58 | ]
59 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/CommandExecutor.node.ts:
--------------------------------------------------------------------------------
1 | import {CommandExecutorBase} from './CommandExecutor.base.js';
2 | import {CommandParameters} from './CommandParameters.js';
3 | import {CommandType} from './CommandType.js';
4 | import {AssociateSchemaCommand} from './AssociateSchemaCommand.js';
5 | import {RemoveSchemaCommand} from './RemoveSchemaCommand.js';
6 |
7 | /**
8 | * Node.js implementation of CommandExecutor with file system support
9 | */
10 | export class CommandExecutor extends CommandExecutorBase {
11 | /**
12 | * Execute the associate schema command (Node.js only - requires file system access)
13 | */
14 | protected async executeAssociateSchema(commandArgs: CommandParameters[CommandType.ASSOCIATE_SCHEMA]): Promise {
15 | const result = AssociateSchemaCommand.execute({
16 | documentUri: commandArgs.documentUri,
17 | schemaPath: commandArgs.schemaPath,
18 | workspaceRoot: this.workspaceRoot
19 | });
20 |
21 | if (result.success) {
22 | this.connection.window.showInformationMessage(result.message);
23 | } else {
24 | this.connection.window.showErrorMessage(result.message);
25 | }
26 |
27 | return result;
28 | }
29 |
30 | /**
31 | * Execute the remove schema command (Node.js only - requires file system access)
32 | */
33 | protected async executeRemoveSchema(commandArgs: CommandParameters[CommandType.REMOVE_SCHEMA]): Promise {
34 | const result = RemoveSchemaCommand.execute({
35 | documentUri: commandArgs.documentUri,
36 | workspaceRoot: this.workspaceRoot
37 | });
38 |
39 | if (result.success) {
40 | this.connection.window.showInformationMessage(result.message);
41 | } else {
42 | this.connection.window.showErrorMessage(result.message);
43 | }
44 |
45 | return result;
46 | }
47 | }
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | KSON Editor
6 |
7 |
8 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/AssociateSchemaCommand.ts:
--------------------------------------------------------------------------------
1 | import { SchemaConfigManager } from './SchemaConfigManager.js';
2 |
3 | export interface AssociateSchemaParams {
4 | documentUri: string;
5 | schemaPath: string;
6 | workspaceRoot: string | null;
7 | }
8 |
9 | export interface AssociateSchemaResult {
10 | success: boolean;
11 | message: string;
12 | }
13 |
14 | /**
15 | * Associates a schema with a document by updating the .kson-schema.kson configuration file.
16 | *
17 | * This command:
18 | * 1. Loads or creates the .kson-schema.kson file
19 | * 2. Adds or updates a schema mapping for the document
20 | * 3. Uses exact file path matching (no wildcards) for precise association
21 | * 4. Saves the updated configuration back to disk
22 | */
23 | export class AssociateSchemaCommand {
24 | /**
25 | * Execute the associate schema command.
26 | *
27 | * @param params Parameters containing document URI, schema path, and workspace root
28 | * @returns Result indicating success or failure with a message
29 | */
30 | static execute(params: AssociateSchemaParams): AssociateSchemaResult {
31 | const {documentUri, schemaPath, workspaceRoot} = params;
32 |
33 | if (!workspaceRoot) {
34 | return {
35 | success: false,
36 | message: 'No workspace root available. Cannot create schema configuration.'
37 | };
38 | }
39 |
40 | try {
41 | const configManager = SchemaConfigManager.load(workspaceRoot);
42 | configManager.associateSchema(documentUri, schemaPath);
43 | configManager.save();
44 |
45 | return {
46 | success: true,
47 | message: `Associated schema "${schemaPath}" with document`
48 | };
49 | } catch (error) {
50 | return {
51 | success: false,
52 | message: `Failed to associate schema: ${error}`
53 | };
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tooling/lsp-clients/monaco/src/KsonEditorManager.ts:
--------------------------------------------------------------------------------
1 | import { MonacoEditorLanguageClientWrapper, WrapperConfig } from 'monaco-editor-wrapper';
2 | import { createKsonLanguageConfig } from './config/ksonConfig.js';
3 | import workerUrl from './worker/ksonServer?worker&url';
4 |
5 |
6 | /**
7 | * Creates a Kson-specific WrapperConfig with sensible defaults.
8 | * You can override any properties by spreading your own config.
9 | */
10 | async function createKsonConfig(overrides: Partial = {}): Promise {
11 | // Create a dedicated worker for this editor
12 | const worker = new Worker(workerUrl, {
13 | type: 'module',
14 | name: `Kson LS (${Math.random().toString(36).substring(2, 15)})`,
15 | });
16 |
17 | const baseConfig = createKsonLanguageConfig({
18 | worker
19 | });
20 |
21 | // Deep merge base config with overrides
22 | const finalConfig: WrapperConfig = {
23 | ...baseConfig,
24 | ...overrides,
25 | vscodeApiConfig: {
26 | ...baseConfig.vscodeApiConfig,
27 | ...overrides.vscodeApiConfig
28 | },
29 | languageClientConfigs: {
30 | ...baseConfig.languageClientConfigs,
31 | ...overrides.languageClientConfigs
32 | },
33 | editorAppConfig: {
34 | ...baseConfig.editorAppConfig,
35 | ...overrides.editorAppConfig,
36 | }
37 | };
38 |
39 | // Attach worker reference for cleanup
40 | (finalConfig as any).__worker = worker;
41 |
42 | return finalConfig;
43 | }
44 |
45 | /**
46 | * Creates a Kson editor wrapper using the standard monaco-editor-wrapper.
47 | * Returns the wrapper which can be managed using its native API.
48 | */
49 | export async function createKsonEditor(config: Partial = {}): Promise {
50 | const ksonConfig = await createKsonConfig(config);
51 |
52 | const wrapper = new MonacoEditorLanguageClientWrapper();
53 | await wrapper.init(ksonConfig);
54 |
55 | return wrapper;
56 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/kotlin/org/kson/jetbrains/KsonPsiFileTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains
2 |
3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase
4 | import com.intellij.util.PsiErrorElementUtil
5 | import org.kson.jetbrains.file.KsonFileType
6 | import org.kson.jetbrains.psi.KsonPsiFile
7 |
8 | class KsonPsiFileTest : BasePlatformTestCase() {
9 |
10 | fun testKsonFileWithoutErrors() {
11 | val psiFile = myFixture.configureByText(KsonFileType, "key: val")
12 | val ksonFile = assertInstanceOf(psiFile, KsonPsiFile::class.java)
13 | assertFalse(PsiErrorElementUtil.hasErrors(project, ksonFile.virtualFile))
14 | }
15 |
16 | fun testKsonFileWithParseError() {
17 | val psiFileWithError = myFixture.configureByText(KsonFileType, "[\"unclosed list\", ")
18 | val ksonFileWithError = assertInstanceOf(psiFileWithError, KsonPsiFile::class.java)
19 | assertTrue(
20 | "should have errors --- since unclosed list error is created during parsing",
21 | PsiErrorElementUtil.hasErrors(project, ksonFileWithError.virtualFile)
22 | )
23 | }
24 |
25 | fun testKsonFileWithPostProcessingIndentError() {
26 | val psiFile = myFixture.configureByText(KsonFileType, """
27 | key: value
28 | bad_indent: value
29 | """.trimIndent())
30 | val ksonFileWithError = assertInstanceOf(psiFile, KsonPsiFile::class.java)
31 | assertFalse(
32 | "should not have errors --- they are provided by an annotator",
33 | PsiErrorElementUtil.hasErrors(project, ksonFileWithError.virtualFile)
34 | )
35 |
36 | // But the annotator should highlight the bad indentation as an error
37 | val highlights = myFixture.doHighlighting()
38 | assertTrue(
39 | "Annotator should detect bad indentation error",
40 | highlights.isNotEmpty()
41 | )
42 | assertTrue(
43 | "Error message should mention indentation",
44 | highlights.any { it.description?.contains("indent", ignoreCase = true) == true }
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib-rust/kson/readme.md:
--------------------------------------------------------------------------------
1 | # Rust bindings for kson-lib API
2 |
3 | The Rust bindings for KSON are split into two crates, following the convention in the Rust
4 | ecosystem:
5 |
6 | - `kson-sys`: the low-level interface to the native library (you probably don't need to use it directly)
7 | - `kson-rs`: the idiomatic wrapper around kson (what you are probably looking for, see the example below)
8 |
9 | ## Example usage
10 |
11 | Add the library to your dependencies:
12 |
13 | ```bash
14 | cargo add kson-rs
15 | ```
16 |
17 | Tell the build where to put the KSON shared library (see [below](#a-note-on-dynamic-linking) for
18 | details):
19 |
20 | ```bash
21 | export KSON_COPY_SHARED_LIBRARY_TO_DIR=target/debug
22 | ```
23 |
24 | Write some code:
25 |
26 | ```rust
27 | use kson_rs::Kson;
28 |
29 | fn main() {
30 | let json = Kson::to_json("key: [1, 2, 3, 4]", false)
31 | .map_err(|_| "unreachable: kson input is guaranteed to be valid!")
32 | .unwrap();
33 | println!("{}", json.output());
34 | }
35 | ```
36 |
37 | Running this with `cargo run` should print the following to stdout:
38 |
39 | ```json
40 | {
41 | "key": [
42 | 1,
43 | 2,
44 | 3,
45 | 4
46 | ]
47 | }
48 | ```
49 |
50 | ## Obtaining kson-lib binaries
51 |
52 | The `kson-sys` crate requires linking to the `kson-lib` binary. Our `build.rs` automatically
53 | downloads a suitable binary from the [kson-binaries
54 | repository](https://github.com/kson-org/kson-binaries), if it can be found. In case no pre-built
55 | binary is available for your platform, you need to manually specify how to obtain it through one of
56 | the following environment variables:
57 |
58 | * `KSON_ROOT_SOURCE_DIR`: if set to the root of a KSON source tree, we will attempt to build and use the necessary binaries from there.
59 | * `KSON_PREBUILT_BIN_DIR`: use pre-built KSON binaries from the specified directory.
60 |
61 | ## A note on dynamic linking
62 |
63 | The KSON bindings use dynamic linking, so you need to make sure the operating system can find the
64 | KSON library at runtime. Hence the `KSON_COPY_SHARED_LIBRARY_TO_DIR` trick, to put the library next
65 | to your binary.
66 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/KsonSettings.ts:
--------------------------------------------------------------------------------
1 | import {LSPAny} from "vscode-languageserver";
2 | import {FormatOptions, FormattingStyle, IndentType} from "kson";
3 |
4 | /**
5 | * Configuration settings for the Kson language server
6 | */
7 | export interface KsonSettings {
8 | kson: {
9 | formatOptions: FormatOptions;
10 | };
11 | }
12 |
13 | /**
14 | * Create new [KsonSettings] from LSP settings, applying defaults where needed.
15 | */
16 | export function ksonSettingsWithDefaults(settings?: LSPAny): Required {
17 | // Create IndentType based on the provided settings
18 | let indentType: IndentType;
19 | if (settings?.kson?.format) {
20 | const format = settings.kson.format;
21 | if (format.insertSpaces === false) {
22 | indentType = IndentType.Tabs;
23 | } else {
24 | // Default to spaces with the specified or default tab size
25 | const tabSize = format.tabSize ?? 2;
26 | indentType = new IndentType.Spaces(tabSize);
27 | }
28 | } else {
29 | // Use the default from the Kotlin library
30 | indentType = new IndentType.Spaces(2);
31 | }
32 |
33 | // Create FormattingStyle based on the provided settings
34 | let formatStyle: FormattingStyle
35 | if (settings?.kson?.format?.formattingStyle) {
36 | // Map lowercase string to uppercase enum value exhaustively
37 | const style = settings.kson.format.formattingStyle.toLowerCase();
38 | switch (style) {
39 | case 'plain':
40 | formatStyle = FormattingStyle.PLAIN;
41 | break;
42 | case 'delimited':
43 | formatStyle = FormattingStyle.DELIMITED;
44 | break;
45 | default:
46 | // Default to PLAIN for any unrecognized value
47 | formatStyle = FormattingStyle.PLAIN;
48 | break;
49 | }
50 | } else {
51 | formatStyle = FormattingStyle.PLAIN;
52 | }
53 |
54 | return {
55 | kson: {
56 | formatOptions: new FormatOptions(indentType, formatStyle)
57 | }
58 | };
59 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/parser/MessageSink.kt:
--------------------------------------------------------------------------------
1 | package org.kson.parser
2 |
3 | import org.kson.stdlibx.collections.toImmutableList
4 | import org.kson.parser.messages.Message
5 | import org.kson.parser.messages.MessageSeverity
6 |
7 | data class LoggedMessage(
8 | val location: Location,
9 | val message: Message
10 | ) {
11 | companion object {
12 | /**
13 | * Print a user-friendly version of a [List] of [LoggedMessage].
14 | *
15 | * Note: locations are output as base-1 indexed firstLine/firstColumn/lastLine/lastColumn numbers
16 | * following [the gnu standard](https://www.gnu.org/prep/standards/html_node/Errors.html)
17 | * for this sort of output
18 | */
19 | fun print(loggedMessages: List): String {
20 | return loggedMessages.joinToString("\n") { loggedMessage ->
21 | val location = loggedMessage.location
22 | val severityLabel = when (loggedMessage.message.type.severity) {
23 | MessageSeverity.ERROR -> "Error"
24 | MessageSeverity.WARNING -> "Warning"
25 | }
26 | "$severityLabel:${location.start}" +
27 | " - ${location.end}, ${
28 | loggedMessage.message
29 | }"
30 | }
31 | }
32 | }
33 | }
34 |
35 | /**
36 | * Collects messages (errors and warnings) during parsing and validation
37 | */
38 | class MessageSink {
39 | private val messages = mutableSetOf()
40 |
41 | fun error(location: Location, message: Message) {
42 | messages.add(LoggedMessage(location, message))
43 | }
44 |
45 | fun hasErrors(): Boolean {
46 | return messages.any { it.message.type.severity == MessageSeverity.ERROR }
47 | }
48 |
49 | fun hasMessages(): Boolean {
50 | return messages.isNotEmpty()
51 | }
52 |
53 | /**
54 | * Return the list of all [LoggedMessage]s sent to this [MessageSink],
55 | * in the order they were logged
56 | */
57 | fun loggedMessages(): List {
58 | return messages.toImmutableList()
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/features/CodeLensService.ts:
--------------------------------------------------------------------------------
1 | import { CodeLens } from 'vscode-languageserver';
2 | import { KsonDocument } from '../document/KsonDocument.js';
3 | import { CommandType } from '../commands/CommandType.js';
4 | import { createTypedCommand } from '../commands/CommandParameters.js';
5 | import { FormattingStyle } from 'kson';
6 |
7 | /**
8 | * Service responsible for providing code lenses for Kson documents
9 | */
10 | export class CodeLensService {
11 |
12 | /**
13 | * Get code lenses for a Kson document.
14 | */
15 | getCodeLenses(document: KsonDocument): CodeLens[] {
16 | const formatCommand = createTypedCommand(
17 | CommandType.PLAIN_FORMAT,
18 | 'plain',
19 | { documentUri: document.uri, formattingStyle: FormattingStyle.PLAIN}
20 | );
21 |
22 | const delimitedFormatCommand = createTypedCommand(
23 | CommandType.DELIMITED_FORMAT,
24 | 'delimited',
25 | { documentUri: document.uri, formattingStyle: FormattingStyle.DELIMITED }
26 | );
27 |
28 | const compactFormatCommand = createTypedCommand(
29 | CommandType.COMPACT_FORMAT,
30 | 'compact',
31 | { documentUri: document.uri, formattingStyle: FormattingStyle.COMPACT }
32 | );
33 |
34 | const classicFormatCommand = createTypedCommand(
35 | CommandType.CLASSIC_FORMAT,
36 | 'classic',
37 | { documentUri: document.uri, formattingStyle: FormattingStyle.CLASSIC }
38 | );
39 |
40 | // Place both lenses at the start of the document
41 | const range = {
42 | start: { line: 0, character: 0 },
43 | end: { line: 0, character: 0 }
44 | };
45 |
46 | return [
47 | {
48 | range,
49 | command: formatCommand
50 | },
51 | {
52 | range,
53 | command: delimitedFormatCommand
54 | },
55 | {
56 | range,
57 | command: classicFormatCommand
58 | },
59 | {
60 | range,
61 | command: compactFormatCommand
62 | }
63 | ];
64 | }
65 | }
--------------------------------------------------------------------------------
/src/commonMain/kotlin/org/kson/parser/behavior/embedblock/EmbedBlockIndent.kt:
--------------------------------------------------------------------------------
1 | package org.kson.parser.behavior.embedblock
2 |
3 |
4 | /**
5 | * This class represents the behavior for handling embedded block content
6 | * by parsing and trimming its minimum indentation.
7 | *
8 | * @property rawEmbedContent The raw embedded block content as a string
9 | * to be analyzed or transformed.
10 | */
11 | class EmbedBlockIndent(embedContent: String) {
12 | private val rawEmbedContent: String = embedContent
13 |
14 | /**
15 | * Computes the minimum indent of all lines in [rawEmbedContent], then returns
16 | * the text with that indent trimmed from each line.
17 | *
18 | * NOTE: blank lines are considered pure indent and used in this calculation, so for instance:
19 | *
20 | * " this string
21 | * has a minimum indent defined
22 | * by its last line
23 | * "
24 | *
25 | * becomes:
26 | *
27 | * " this string
28 | * has a minimum indent defined
29 | * by its blank last line
30 | * "
31 | */
32 | fun trimMinimumIndent(): String {
33 | val minCommonIndent = computeMinimumIndent()
34 |
35 | return rawEmbedContent
36 | .split("\n")
37 | .joinToString("\n") { it.drop(minCommonIndent) }
38 | }
39 |
40 | /**
41 | * Computes the minimum indent in a [rawEmbedContent].
42 | *
43 | * NOTE: blank lines are considered pure indent and used in this calculation, so for instance:
44 | *
45 | * " this string
46 | * has a minimum indent defined
47 | * by its last line
48 | * "
49 | *
50 | * returns: 2
51 | */
52 | fun computeMinimumIndent(): Int {
53 | val linesWithNewlines = rawEmbedContent.split("\n").map { it + "\n" }
54 |
55 | val minCommonIndent =
56 | linesWithNewlines.minOfOrNull { it.indexOfFirst { char -> !isInlineWhitespace(char) } } ?: 0
57 |
58 | return minCommonIndent
59 | }
60 |
61 | /**
62 | * Returns true if the given [char] is a non-newline whitespace
63 | */
64 | private fun isInlineWhitespace(char: Char?): Boolean {
65 | return char == ' ' || char == '\r' || char == '\t'
66 | }
67 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/parser/KsonParserDefinition.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.parser
2 |
3 | import com.intellij.lang.ASTNode
4 | import com.intellij.lang.ParserDefinition
5 | import com.intellij.lang.PsiParser
6 | import com.intellij.lexer.Lexer
7 | import com.intellij.openapi.project.Project
8 | import com.intellij.psi.FileViewProvider
9 | import com.intellij.psi.PsiElement
10 | import com.intellij.psi.PsiFile
11 | import com.intellij.psi.tree.IFileElementType
12 | import com.intellij.psi.tree.TokenSet
13 | import org.kson.jetbrains.KsonLanguage
14 | import org.kson.jetbrains.psi.KsonPsiElement
15 | import org.kson.jetbrains.psi.KsonPsiFile
16 | import org.kson.parser.ParsedElementType
17 | import org.kson.parser.TokenType
18 |
19 | class KsonParserDefinition : ParserDefinition {
20 | override fun createLexer(project: Project?): Lexer {
21 | return KsonLexer()
22 | }
23 |
24 | override fun createParser(project: Project?): PsiParser {
25 | return KsonParser()
26 | }
27 |
28 | override fun getFileNodeType(): IFileElementType {
29 | return IFileElementType(KsonLanguage)
30 | }
31 |
32 | override fun getCommentTokens(): TokenSet {
33 | return commentTokenSet
34 | }
35 |
36 | override fun getWhitespaceTokens(): TokenSet {
37 | return whitespaceTokenSet
38 | }
39 |
40 | override fun getStringLiteralElements(): TokenSet {
41 | return stringTokenSet
42 | }
43 |
44 | override fun createElement(node: ASTNode): PsiElement {
45 | return when (node.elementType) {
46 | elem(ParsedElementType.EMBED_BLOCK) -> org.kson.jetbrains.psi.KsonEmbedBlock(node)
47 | elem(TokenType.EMBED_CONTENT) -> org.kson.jetbrains.psi.KsonEmbedContent(node)
48 | else -> KsonPsiElement(node)
49 | }
50 | }
51 |
52 | override fun createFile(viewProvider: FileViewProvider): PsiFile {
53 | return KsonPsiFile(viewProvider)
54 | }
55 |
56 | }
57 |
58 | private val commentTokenSet = TokenSet.create(elem(TokenType.COMMENT))
59 | private val whitespaceTokenSet = TokenSet.create(elem(TokenType.WHITESPACE))
60 | private val stringTokenSet =
61 | TokenSet.create(elem(ParsedElementType.OBJECT_KEY), elem(TokenType.STRING_CONTENT), elem(TokenType.UNQUOTED_STRING))
62 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/org/kson/schema/validators/AnyOfValidatorTest.kt:
--------------------------------------------------------------------------------
1 | package org.kson.schema.validators
2 |
3 | import org.kson.parser.messages.MessageType.*
4 | import org.kson.schema.JsonSchemaTest
5 | import kotlin.test.Test
6 |
7 | class AnyOfValidatorTest : JsonSchemaTest {
8 | @Test
9 | fun testAnyOfCommonValidationErrors() {
10 | assertKsonSchemaErrors(
11 | """
12 | description: 99
13 | thing: false
14 | """.trimIndent(),
15 | """
16 | anyOf:
17 | - properties:
18 | description:
19 | type: string
20 | .
21 | thing:
22 | type: number
23 |
24 | - properties:
25 | description:
26 | type: string
27 | .
28 | .
29 | required:
30 | - required_prop
31 | """.trimIndent(),
32 | listOf(
33 | // note that since `description` is wrong for ALL the sub-schemas, we get a precise error for it.
34 | SCHEMA_VALUE_TYPE_MISMATCH
35 | )
36 | )
37 | }
38 |
39 | @Test
40 | fun testAnyOfDiverseValidationErrors() {
41 | assertKsonSchemaErrors(
42 | """
43 | description: "describer"
44 | think: false
45 | """.trimIndent(),
46 | """
47 | anyOf:
48 | - properties:
49 | description:
50 | type: string
51 | .
52 | think:
53 | type: number
54 |
55 | - properties:
56 | description:
57 | type: string
58 | .
59 | .
60 | required:
61 | - required_prop
62 | """.trimIndent(),
63 | listOf(
64 | SCHEMA_ANY_OF_VALIDATION_FAILED,
65 | // sub-schema errors are rolled up into this error
66 | SCHEMA_SUB_SCHEMA_ERRORS)
67 | )
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tooling/cli/src/main/kotlin/org/kson/tooling/cli/CommandLineInterface.kt:
--------------------------------------------------------------------------------
1 | package org.kson.tooling.cli
2 |
3 | import com.github.ajalt.clikt.core.*
4 | import com.github.ajalt.clikt.parameters.options.versionOption
5 | import org.kson.tooling.cli.commands.JsonCommand
6 | import org.kson.tooling.cli.commands.YamlCommand
7 | import org.kson.tooling.cli.commands.ValidateCommand
8 | import org.kson.tooling.cli.commands.KsonFormatCommand
9 | import org.kson.tooling.cli.generated.KSON_VERSION
10 |
11 |
12 | class KsonCli : CliktCommand(name = "kson") {
13 | init {
14 | versionOption(KSON_VERSION, names = setOf("--version", "-V"))
15 | }
16 | override fun help(context: Context) = """
17 | |KSON CLI - A tool for working with KSON (KSON Structured Object Notation) files.
18 | |
19 | |KSON is a human-friendly data serialization format that supports JSON and YAML conversion.
20 | |Use the subcommands to transpile, analyze, or validate KSON documents.
21 | |
22 | |Examples:
23 | |${"\u0085"}Convert KSON to JSON:
24 | |${"\u0085"} kson json -i input.kson -o output.json
25 | |${"\u0085"}
26 | |${"\u0085"}Convert KSON to YAML:
27 | |${"\u0085"} kson yaml -i input.kson -o output.yaml
28 | |${"\u0085"}
29 | |${"\u0085"}Format KSON with custom formatting options:
30 | |${"\u0085"} kson format -i input.kson --indent-spaces 4 -o formatted.kson
31 | |${"\u0085"}
32 | |${"\u0085"}Analyze KSON for errors:
33 | |${"\u0085"} kson analyze -i file.kson
34 | |${"\u0085"}
35 | |${"\u0085"}Validate against a schema:
36 | |${"\u0085"} kson json -i input.kson -s schema.kson -o output.json
37 | |${"\u0085"}
38 | |${"\u0085"}Read from stdin (use - or omit filename):
39 | |${"\u0085"} cat data.kson | kson json
40 | |
41 | |For more help on a specific command, use: kson --help
42 | """.trimMargin()
43 |
44 | init {
45 | context {
46 | allowInterspersedArgs = false
47 | }
48 | }
49 |
50 | override fun run() = Unit
51 | }
52 |
53 | fun main(args: Array) {
54 | KsonCli()
55 | .subcommands(
56 | KsonFormatCommand(),
57 | ValidateCommand(),
58 | JsonCommand(),
59 | YamlCommand(),
60 | )
61 | .main(args)
62 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/main/kotlin/org/kson/jetbrains/pages/KsonColorSettingsPage.kt:
--------------------------------------------------------------------------------
1 | package org.kson.jetbrains.pages
2 |
3 | import com.intellij.openapi.editor.colors.TextAttributesKey
4 | import com.intellij.openapi.fileTypes.SyntaxHighlighter
5 | import com.intellij.openapi.options.colors.AttributesDescriptor
6 | import com.intellij.openapi.options.colors.ColorDescriptor
7 | import com.intellij.openapi.options.colors.ColorSettingsPage
8 | import org.kson.jetbrains.KsonBundle
9 | import org.kson.jetbrains.KsonIcons
10 | import org.kson.jetbrains.highlighter.KsonSyntaxHighlighter
11 | import javax.swing.Icon
12 |
13 | class KsonColorSettingsPage : ColorSettingsPage {
14 | override fun getIcon(): Icon {
15 | return KsonIcons.FILE
16 | }
17 |
18 | override fun getHighlighter(): SyntaxHighlighter {
19 | return KsonSyntaxHighlighter()
20 | }
21 |
22 | override fun getDemoText(): String {
23 | return """
24 | key: value
25 | string: "a string"
26 | dashList:
27 | - "list element"
28 | - <
29 | - "element of delimited sub-list"
30 | - "another sub-list element"
31 | >
32 | - "another list element"
33 | list: [1, 2, 3, true, false]
34 | invalid: ``
35 | embed_block: %%kotlin
36 | println("Hello y'all")
37 | %%
38 | # this is a comment
39 | nested: {
40 | null_keyword: null
41 | }
42 | """.trimIndent()
43 | }
44 |
45 | override fun getAdditionalHighlightingTagToDescriptorMap(): Map? {
46 | return null
47 | }
48 |
49 | override fun getAttributeDescriptors(): Array {
50 | return DESCRIPTORS
51 | }
52 |
53 | override fun getColorDescriptors(): Array {
54 | return ColorDescriptor.EMPTY_ARRAY
55 | }
56 |
57 | override fun getDisplayName(): String {
58 | return KsonBundle.message("kson.name")
59 | }
60 |
61 | companion object {
62 | private val DESCRIPTORS = KsonSyntaxHighlighter.KsonColorTag.values().map {
63 | AttributesDescriptor(
64 | it.displayName,
65 | KsonSyntaxHighlighter.getTextAttributesKey(it)
66 | )
67 | }.toTypedArray()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tooling/language-server-protocol/src/core/commands/RemoveSchemaCommand.ts:
--------------------------------------------------------------------------------
1 | import { SchemaConfigManager } from './SchemaConfigManager.js';
2 | import { SCHEMA_CONFIG_FILENAME } from '../schema/SchemaConfig.js';
3 |
4 | export interface RemoveSchemaParams {
5 | documentUri: string;
6 | workspaceRoot: string | null;
7 | }
8 |
9 | export interface RemoveSchemaResult {
10 | success: boolean;
11 | message: string;
12 | }
13 |
14 | /**
15 | * Removes a schema association from a document by updating the .kson-schema.kson configuration file.
16 | *
17 | * This command:
18 | * 1. Loads the .kson-schema.kson file
19 | * 2. Removes the schema mapping for the specified document
20 | * 3. Saves the updated configuration back to disk (or deletes the file if empty)
21 | */
22 | export class RemoveSchemaCommand {
23 | /**
24 | * Execute the remove schema command.
25 | *
26 | * @param params Parameters containing document URI and workspace root
27 | * @returns Result indicating success or failure with a message
28 | */
29 | static execute(params: RemoveSchemaParams): RemoveSchemaResult {
30 | const {documentUri, workspaceRoot} = params;
31 |
32 | if (!workspaceRoot) {
33 | return {
34 | success: false,
35 | message: 'No workspace root available. Cannot modify schema configuration.'
36 | };
37 | }
38 |
39 | try {
40 | const configManager = SchemaConfigManager.tryLoad(workspaceRoot);
41 |
42 | if (!configManager) {
43 | return {
44 | success: false,
45 | message: `No schema configuration file found at ${SCHEMA_CONFIG_FILENAME}`
46 | };
47 | }
48 |
49 | const removed = configManager.removeSchema(documentUri);
50 |
51 | if (!removed) {
52 | return {
53 | success: false,
54 | message: `No schema association found for document`
55 | };
56 | }
57 |
58 | configManager.save();
59 |
60 | return {
61 | success: true,
62 | message: `Removed schema association from document`
63 | };
64 | } catch (error) {
65 | return {
66 | success: false,
67 | message: `Failed to remove schema association: ${error}`
68 | };
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/tooling/jetbrains/src/test/resources/testData/parser/EmptyCommaError.txt:
--------------------------------------------------------------------------------
1 | FILE(0,28)
2 | KsonPsiElement([Kson-parsed] BRACKET_LIST)(0,28)
3 | PsiElement(IElementTokenType(tokenType=SQUARE_BRACKET_L))('[')(0,1)
4 | PsiErrorElement:Redundant comma found. A comma must delimit a value, one comma per value(1,5)
5 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(1,2)
6 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(2,3)
7 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(3,4)
8 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(4,5)
9 | KsonPsiElement([Kson-parsed] LIST_ELEMENT)(6,10)
10 | KsonPsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))(6,7)
11 | PsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))('x')(6,7)
12 | PsiErrorElement:Redundant comma found. A comma must delimit a value, one comma per value(8,10)
13 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(8,9)
14 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(9,10)
15 | KsonPsiElement([Kson-parsed] LIST_ELEMENT)(11,20)
16 | KsonPsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))(11,12)
17 | PsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))('y')(11,12)
18 | PsiErrorElement:Redundant comma found. A comma must delimit a value, one comma per value(13,20)
19 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(13,14)
20 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(14,15)
21 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(15,16)
22 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(16,17)
23 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(17,18)
24 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(18,19)
25 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(19,20)
26 | KsonPsiElement([Kson-parsed] LIST_ELEMENT)(21,27)
27 | KsonPsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))(21,22)
28 | PsiElement(IElementTokenType(tokenType=UNQUOTED_STRING))('z')(21,22)
29 | PsiErrorElement:Redundant comma found. A comma must delimit a value, one comma per value(23,27)
30 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(23,24)
31 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(24,25)
32 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(25,26)
33 | PsiElement(IElementTokenType(tokenType=COMMA))(',')(26,27)
34 | PsiElement(IElementTokenType(tokenType=SQUARE_BRACKET_R))(']')(27,28)
35 |
--------------------------------------------------------------------------------