diff --git a/src/plugins/StreamParserGcode.ts b/src/plugins/StreamParserGcode.ts
index 3f4a0c0b8..c070b31e9 100644
--- a/src/plugins/StreamParserGcode.ts
+++ b/src/plugins/StreamParserGcode.ts
@@ -8,41 +8,51 @@ export const gcode = {
/* Klipper macro attributes */
if (stream.pos > zeroPos && state.klipperMacro) {
- if (stream.match(/^\s*[A-Z_]+/)) return 'propertyName'
- else if (stream.match(/^\s*[A-Za-z0-9_]+/)) return 'number'
- else if (stream.match(/^{.*}/)) return 'variable'
+ stream.eatSpace()
+ if (stream.match(/^(".+"|true|false)/i)) {
+ return 'string'
+ } else if (stream.match(/^\d+/)) return 'number'
+ else if (stream.match(/^[A-Za-z\d_]+/)) return 'propertyName'
+ else if (zeroPos === 0 && stream.match(/^{[^%]+}/)) return 'variable'
}
/* comments */
- if ([';', '#'].includes(ch ?? '')) {
+ if ([';'].includes(ch ?? '')) {
stream.skipToEnd()
return 'comment'
}
+ const isZero = stream.pos == zeroPos
+
/* Mxxx Gxxx commands */
- if (stream.pos == zeroPos && stream.match(/[GMgm][\d]+/)) {
+ if (isZero && stream.match(/_?[GMgm][\d.]+/)) {
return 'namespace'
}
+ if (stream.string.substr(zeroPos).toLowerCase().startsWith('m117')) {
+ stream.skipToEnd()
+ return 'string'
+ }
+
/* G0/1 movements */
- if (stream.pos > zeroPos && stream.match(/[XYZIJxyzij]-?([\d]*\.[\d]+|[\d]+)?/)) {
+ if (stream.pos > zeroPos && stream.match(/[EPXYZIJ]-?([\d]*\.[\d]+|[\d]+)?/i)) {
return 'className'
}
/* G0/1 speeds */
- if (stream.pos > zeroPos && stream.match(/[Ff]-?([\d]*\.[\d]+|[\d]+)/)) {
+ if (stream.pos > zeroPos && stream.match(/[Ff]-?([\d]*\.[\d]+|[\d]+)?/)) {
return 'string'
}
/* G0/1 extrusions */
- if (stream.pos > zeroPos && stream.match(/[TtSsEe]-?([\d]*\.[\d]+|[\d]+)/)) {
+ if (stream.pos > zeroPos && stream.match(/[TtSs]-?([\d]*\.[\d]+|[\d]+)?/)) {
return 'atom'
}
- if (stream.pos > zeroPos && stream.match(/^{.*}/)) return 'propertyName'
+ if (zeroPos === 0 && stream.pos > zeroPos && stream.match(/^{[^%]+}/)) return 'propertyName'
/* Klipper macro names */
- if (stream.pos == zeroPos && stream.match(/^[A-Z_]+/)) {
+ if (isZero && stream.match(/^\s*[A-Z_\d]+/)) {
state.klipperMacro = true
return 'name'
}
diff --git a/src/plugins/StreamParserKlipperConfig.ts b/src/plugins/StreamParserKlipperConfig.ts
index 6da01ba28..35cab04cf 100644
--- a/src/plugins/StreamParserKlipperConfig.ts
+++ b/src/plugins/StreamParserKlipperConfig.ts
@@ -1,130 +1,172 @@
-import { StringStream } from '@codemirror/stream-parser'
+import { StreamParser, StringStream } from '@codemirror/stream-parser'
import { gcode } from '@/plugins/StreamParserGcode'
-export const klipper_config = {
+export const klipper_config: StreamParser
= {
token: function (stream: StringStream, state: StreamParserKlipperConfigState): string | null {
const ch = stream.peek()
-
/* comments */
if (
- stream.match(/^\s+#/) ||
- (ch === '#' && (stream.pos === 0 || /\s/.test(stream.string.charAt(stream.pos - 1))))
+ stream.match(/^\s+[#;]/) ||
+ ([';', '#'].includes(ch ?? '') && (stream.pos === 0 || /\s/.test(stream.string.charAt(stream.pos - 1))))
) {
stream.skipToEnd()
+ state.block = false
state.pair = false
return 'comment'
}
- if (state.gcode && !state.klipperMacroJinja && ch === '[') {
- state.gcode = false
- state.gcodeZeroPos = null
+ if (ch !== '[' && stream.indentation() === 0 && stream.sol() && stream.match(/^[^:]+$/i)) {
+ stream.skipToEnd()
+ return null
}
- if (state.gcode) {
- stream.eatSpace()
- if (state.gcodeZeroPos === null) {
- state.gcodeZeroPos = stream.pos
+ if (stream.indentation() === 0) {
+ if (stream.pos === 0 && ch === '[') {
+ state.block = true
+ stream.next()
+ return null
}
- if (state.klipperMacroJinja) {
- if (stream.match(/^%?}/)) {
- state.klipperMacroJinja = false
+ if (state.block) {
+ if (!ch || ch === ']' || stream.eol()) {
+ stream.next()
+ state.block = false
return null
}
- if (stream.eat(/.(?!%})/)) {
- return 'string'
+ if (stream.match(/^\s[^\]]+/)) {
+ return 'className'
}
- if (stream.eat(/.(?!})/)) {
- return 'string'
+ if (stream.match(/^[^ \]]+/)) {
+ return 'namespace'
}
- stream.match(/^.*$/)
- return 'string'
- }
-
- if (stream.match(/^{%?/)) {
- state.klipperMacroJinja = true
- return null
}
+ if (state.gcode) {
+ if (stream.sol() || stream.eol()) {
+ state.gcode = false
+ state.gcodeZeroPos = null
+ return null
+ }
- return gcode.token(stream, state, state.gcodeZeroPos)
- }
+ if (!state.gcodeZeroPos) {
+ stream.eatSpace()
+ state.gcodeZeroPos = stream.pos
+ }
- if (stream.match(/^gcode:\s*/)) {
- stream.skipToEnd()
- state.gcode = true
- return 'atom'
- }
+ if (state.klipperMacroJinja) {
+ if (
+ (state.klipperMacroJinjaPercent && stream.match(/^%}/)) ||
+ (!state.klipperMacroJinjaPercent && stream.match(/^}/))
+ ) {
+ state.klipperMacroJinja = false
+ state.klipperMacroJinjaPercent = false
+ stream.eatSpace()
+ state.gcodeZeroPos = stream.pos
+ return null
+ }
+ stream.next()
+ return 'string'
+ }
- if (state.pair) {
- if (stream.match(/^[\^!]*[a-z_]+[0-9]*?:/i)) {
- stream.backUp(1)
- return 'macroName'
- } else if (
- stream.match(/^[\^!]*(?:(?:P[A-Z]?|A[A-Z]?|EXP|GPIOCHIP|GPIO)+[0-9.]+|z_virtual_endstop)(_[0-9]+)?/i) ||
- stream.match(/^\/gpio[0-9]+/i)
- ) {
- if (stream.eol()) {
- state.pair = false
+ if (stream.match(/^\s*{[%{]?/)) {
+ state.klipperMacroJinjaPercent = stream.string.includes('{%')
+ state.klipperMacroJinja = true
+ return null
}
- return 'macroName'
- } else if (stream.match(/^\s*\[?(-?[0-9,.:]+)]?/)) {
- if (stream.eol()) {
- state.pair = false
+ return gcode.token(stream, state, state.gcodeZeroPos ?? 0)
+ }
+ } else {
+ state.was = true
+ if (state.gcode) {
+ if (stream.sol()) {
+ stream.eatSpace()
+ state.gcodeZeroPos = stream.pos
}
- return 'number'
- } else if (stream.match(/^([a-zA-Z0-9~"'.,:_/\-\s]+)/)) {
- if (stream.eol()) {
- state.pair = false
+ if (state.klipperMacroJinja) {
+ if (
+ (state.klipperMacroJinjaPercent && stream.match(/^%}/)) ||
+ (!state.klipperMacroJinjaPercent && stream.match(/^}/))
+ ) {
+ state.klipperMacroJinja = false
+ state.klipperMacroJinjaPercent = false
+ stream.eatSpace()
+ state.gcodeZeroPos = stream.pos
+ return null
+ }
+ stream.next()
+ return 'string'
}
- return 'string'
- } else if (stream.match(/^\s*#.*$/)) {
- stream.skipToEnd()
- state.pair = false
- return 'comment'
+
+ if (stream.match(/^\s*{[%{]?/)) {
+ state.klipperMacroJinjaPercent = stream.string.includes('{%')
+ state.klipperMacroJinja = true
+ return null
+ }
+ return gcode.token(stream, state, state.gcodeZeroPos ?? stream.pos)
+ } else if (state.pair) {
+ stream.eatSpace()
+ if (ch !== ',') {
+ if (stream.match(/^-?\d*\.?(?:\d+)?($|,)/)) {
+ return 'number'
+ }
+ if (stream.match(/^[^#]+/)) {
+ return 'string'
+ }
+ }
+ stream.next()
+ return null
}
- stream.next()
- return null
}
+ if (state.was && stream.indentation() === 0) {
+ state.pair = false
+ state.gcode = false
+ state.was = false
+ }
+
+ if (!state.pair && !state.gcode && stream.sol()) {
+ if (stream.match(/^(?:[A-Za-z]*_?gcode|enable):/)) {
+ state.gcode = true
+ } else {
+ stream.match(/^.+?:\s*/)
+ state.pair = !stream.eol()
+ }
- if (stream.match(/^[a-zA-Z0-9_]+\s?[:=]\s?/)) {
- state.pair = true
return 'atom'
}
- /*if (stream.match(/^\[include.*]$/)) {
- console.log(stream, state);
- return "macroName";
- }*/
-
- if (state.block) {
- if (stream.match(/^[a-z0-9-_]+/i)) {
- return 'namespace'
+ if (state.pair) {
+ if (ch === ':') {
+ stream.next()
+ stream.eatSpace()
+ return null
}
- if (stream.match(/^\s[a-z0-9-_./*\s]+/i)) {
- return 'className'
+
+ if (!ch || stream.eol()) {
+ state.pair = false
+ return null
}
- stream.skipToEnd()
- state.block = false
- return null
- }
- if (stream.next() === '[') {
- state.block = true
- return null
+ if (stream.match(/^(-?\d*\.?(?:\d+)?(,|$|\s))+/)) {
+ state.pair = false
+ return 'number'
+ }
+ if (stream.match(/^[^#]+/)) {
+ state.pair = false
+ return 'string'
+ }
}
- if (stream.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/)) return 'string'
-
stream.next()
- return 'string'
+ return null
},
startState: function (): StreamParserKlipperConfigState {
return {
block: false,
pair: false,
+ was: false,
gcode: false,
klipperMacro: false,
gcodeZeroPos: null,
klipperMacroJinja: false,
+ klipperMacroJinjaPercent: false,
}
},
languageData: {
@@ -135,8 +177,10 @@ export const klipper_config = {
interface StreamParserKlipperConfigState {
block: boolean
pair: boolean
+ was: boolean
gcode: boolean
gcodeZeroPos: number | null
klipperMacro: boolean
klipperMacroJinja: boolean
+ klipperMacroJinjaPercent: boolean
}