-
-
Couldn't load subscription status.
- Fork 44
Description
I got bugged by the lack of error report highlighting and dug into this a bit.
First I got highlighting going with my own diagnostics from mapping into a config shape, which meant digging into the highlighting support in miette
syntect which is the default highlighting used by miette requires a .sublime-syntax source to define the grammar, so I translated by hand the .tmLanguage.json in the vscode extension (since as far as I could google you need Sublime Text 3 to automatically translate), feel free to use if it's handy (I had some issue with includes so I unwrapped those, but it looks like it should work from the source?)
kdl.sublime-syntax
version: 2
name: kdl
file_extensions: [ kdl ]
scope: source.kdl
contexts:
main:
- scope: "invalid.illegal.kdl.bad-ident"
match: "(?<!#)(?:true|false|null|nan|[-]?inf)"
- scope: "constant.language.null.kdl"
match: "#null"
- scope: "constant.language.boolean.kdl"
match: "#true|#false"
- scope: "constant.language.other.kdl"
match: "#nan|#inf|#-inf"
- comment: "Floating point literal (fraction)"
scope: "constant.numeric.float.rust"
match: "\\b([0-9\\-\\+]|\\-|\\+)[0-9_]*\\.[0-9][0-9_]*([eE][+-]?[0-9_]+)?\\b"
- comment: "Floating point literal (exponent)"
scope: "constant.numeric.float.rust"
match: "\\b[0-9][0-9_]*(\\.[0-9][0-9_]*)?[eE][+-]?[0-9_]+\\b"
- comment: "Integer literal (decimal)"
scope: "constant.numeric.integer.decimal.rust"
match: "\\b[0-9\\-\\+][0-9_]*\\b"
- comment": "Integer literal (hexadecimal)"
scope: "constant.numeric.integer.hexadecimal.rust"
match: "\\b0x[a-fA-F0-9][a-fA-F0-9_]*\\b"
- comment: "Integer literal (octal)"
scope: "constant.numeric.integer.octal.rust"
match: "\\b0o[0-7][0-7_]*\\b"
- comment: "Integer literal (binary)"
scope: "constant.numeric.integer.binary.rust"
match: "\\b0b[01][01_]*\\b"
- embed: "string.quoted.other.raw.kdl"
match: "(#+)(\"\"\"|\")"
captures:
0: punctuation.definition.string.begin.kdl
escape: "\\2\\1"
escape_captures:
0: punctuation.definition.string.end.kdl
- match: '"""'
push: string_multi_line
- match: '"'
push: string_single_line
- match: "/\\*"
push: block_comment
- match: "/\\*[\\*!](?![\\*/])"
push: block_doc_comment
- comment: "Slashdash block comment"
match: "/-\\s*{"
push: slashdash_block_comment
- comment: "Slashdash inline comment"
match: "(?<!^)\\s*/-\\s*"
push: slashdash_comment
- comment: "Slashdash node comment"
match: "(?<=^)\\s*/-[^{]+$"
push: slashdash_node_comment
- comment: "Slashdash node comment"
match: "(?<=^)\\s*/-[^{]+{"
push: slashdash_node_with_children_comment
- comment: "Single-line comment"
match: "//"
push: line_comment
- scope: "entity.other.attribute-name.kdl"
match: "(?![/\\\\{\\}#;\\[\\]\\=])[<>:\\w\\-_~,'`!\\?@\\$%^&*+|.\\(\\)]+\\d*[<>:\\w\\-_~,'`!\\?@\\$%^&*+|.\\(\\)]*(=)"
captures:
1: "punctuation.separator.key-value.kdl"
- scope: "entity.name.tag.kdl"
match: "((?<={|;)|^)\\s*(?![/\\\\{\\}#;\\[\\]\\=])[<>:\\w\\-_~,'`!\\?@\\$%^&*+|.\\(\\)]+\\d*[<>:\\w\\-_~,'`!\\?@\\$%^&*+|.\\(\\)]*"
- scope: "string.unquoted.kdl"
match: "(?![/\\\\{\\}#;\\[\\]\\=])[<>:\\w\\-_~,'`!\\?@\\$%^&*+|.\\(\\)]+\\d*[<>:\\w\\-_~,'`!\\?@\\$%^&*+|.\\(\\)]*"
string_multi_line:
- meta_scope: "string.quoted.triple.kdl"
- scope: "constant.character.escape.kdl"
match: "\\\\(:?[nrtbfs\\\\\"]|u\\{[a-fA-F0-9]{1,6}\\})"
- match: '"""'
pop: true
string_single_line:
- meta_scope: "string.quoted.double.kdl"
- scope: "constant.character.escape.kdl"
match: "\\\\(:?[nrtbfs\\\\\"]|u\\{[a-fA-F0-9]{1,6}\\})"
- match: '"'
pop: true
block_comment:
- comment: "Block comment"
meta_scope: "comment.block.kdl"
# syntect doesn't support - include: block_comment?
- match: "/\\*[\\*!](?![\\*/])"
push: block_doc_comment
- match: "/\\*"
push: block_comment
- match: "\\*/"
pop: true
block_doc_comment:
- comment: "Block documentation comment"
meta_scope: "comment.block.documentation.kdl"
- match: "/\\*[\\*!](?![\\*/])"
push: block_doc_comment
- match: "/\\*"
push: block_comment
- match: "\\*/"
pop: true
slashdash_block_comment:
- meta_scope: "comment.block.slashdash.kdl"
match: "\\}"
pop: true
slashdash_comment:
- meta_scope: "comment.block.slashdash.kdl"
- match: "\\s"
pop: true
slashdash_node_comment:
- meta_scope: "comment.block.slashdash.kdl"
- match: "(?:;|(?<!\\\\)$)"
pop: true
slashdash_node_with_children_comment:
- meta_scope: "comment.block.slashdash.kdl"
- match: "\\}"
pop: true
line_comment:
- meta_scope: "comment.line.double-slash.kdl"
- match: "$"
pop: trueThen you can register this in miette with some horrible code like this (in the application):
fn main() -> miette::Result<()> {
miette::set_hook(Box::new(miette_hook))?;
...
}
fn highlight_theme() -> syntect::highlighting::Theme {
syntect::highlighting::ThemeSet::load_defaults()
.themes
.remove("Solarized (dark)")
.expect("Couldn't load theme")
}
fn parse_kdl_syntax() -> syntect::parsing::SyntaxDefinition {
syntect::parsing::SyntaxDefinition::load_from_str(
include_str!("kdl.sublime-syntax"),
false,
None,
)
.expect("Failed to load KDL syntax definition")
}
fn miette_hook(_: &(dyn miette::Diagnostic + 'static)) -> Box<dyn miette::ReportHandler> {
let mut syntax_set = syntect::parsing::SyntaxSetBuilder::new();
syntax_set.add(parse_kdl_syntax());
let syntax_set = syntax_set.build();
let theme = highlight_theme();
let highlighter = miette::highlighters::SyntectHighlighter::new(syntax_set, theme, false);
Box::new(
miette::MietteHandlerOpts::new()
.with_syntax_highlighting(highlighter)
.build(),
)
}Finally, you attach a SourceFile that declares this is a KDL file, e.g. with NamedSource :
match Config::parse(text) {
Err(error) => {
let report = miette::Report::new(error)
.with_source_code(miette::NamedSource::new(path, text).with_language("kdl"));
eprintln!("{report:?}");
std::process::exit(1);
}
Ok(config) => ...
}(Though I'm sure there's better options - this is the simplest I found that doesn't need bubbling miette::Report to main)
Unfortunately, this doesn't work with the parse errors issued by kdl as both the KdlError and each KdlDiagnostic include a #[source_code] source_code: Arc<String> which trumps any outer #[source_code].
I'm not quite sure what the right option to fix that is - maybe just switching to:
struct KdlSource(Arc<String>);
impl miette::SourceCode for KdlSource {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
let contents = (**self).read_span(span, context_lines_before, context_lines_after)?;
Ok(contents.with_language("kdl"))
}
}