Improve normalization

This commit is contained in:
Severin Müller 2025-10-09 08:26:50 +02:00
parent 7b9410c36d
commit 2ce5623712
3 changed files with 76 additions and 14 deletions

View File

@ -326,7 +326,9 @@ type SerialSettings struct {
EnableEcho bool `json:"enableEcho"` // Whether to echo received characters back to the sender
NormalizeMode string `json:"normalizeMode"` // Normalization mode: "carret", "names", "hex"
NormalizeLineEnd string `json:"normalizeLineEnd"` // Line ending normalization: "keep", "lf", "cr", "crlf"
TabRender string `json:"tabRender"` // How to render tabs: "spaces", "arrow", "pipe"
PreserveANSI bool `json:"preserveANSI"` // Whether to preserve ANSI escape codes
ShowNLTag bool `json:"showNLTag"` // Whether to show a special tag for new lines
Buttons []QuickButton `json:"buttons"` // Custom quick buttons
}
@ -438,7 +440,7 @@ func getSerialSettings() (SerialSettings, error) {
if consoleBroker != nil {
norm := NormOptions{
Mode: normalizeMode, CRLF: crlfMode, TabRender: "", PreserveANSI: serialConfig.PreserveANSI,
Mode: normalizeMode, CRLF: crlfMode, TabRender: serialConfig.TabRender, PreserveANSI: serialConfig.PreserveANSI, ShowNLTag: serialConfig.ShowNLTag,
}
consoleBroker.SetNormOptions(norm)
}
@ -533,7 +535,7 @@ func setSerialSettings(newSettings SerialSettings) error {
if consoleBroker != nil {
norm := NormOptions{
Mode: normalizeMode, CRLF: crlfMode, TabRender: "", PreserveANSI: serialConfig.PreserveANSI,
Mode: normalizeMode, CRLF: crlfMode, TabRender: serialConfig.TabRender, PreserveANSI: serialConfig.PreserveANSI, ShowNLTag: serialConfig.ShowNLTag,
}
consoleBroker.SetNormOptions(norm)
}

View File

@ -46,6 +46,7 @@ type NormOptions struct {
CRLF CRLFMode
TabRender string // e.g. " " or "" to keep '\t'
PreserveANSI bool
ShowNLTag bool // <- NEW: also print a visible tag for CR/LF
}
func normalize(in []byte, opt NormOptions) string {
@ -88,42 +89,68 @@ func normalize(in []byte, opt NormOptions) string {
}
}
// CR/LF normalization
// CR/LF normalization (emit real newline(s), optionally tag them visibly)
if b == '\r' || b == '\n' {
// detect pair (CRLF or LFCR)
isPair := i+1 < len(in) &&
((b == '\r' && in[i+1] == '\n') || (b == '\n' && in[i+1] == '\r'))
// optional visible tag of what we *saw*
if opt.ShowNLTag {
if isPair {
if b == '\r' { // saw CRLF
out.WriteString("<CRLF>")
} else { // saw LFCR
out.WriteString("<LFCR>")
}
} else {
if b == '\r' {
out.WriteString("<CR>")
} else {
out.WriteString("<LF>")
}
}
}
// now emit the actual newline(s) per the normalization mode
switch opt.CRLF {
case CRLFAsIs:
out.WriteByte(b)
i++
if isPair {
out.WriteByte(b)
out.WriteByte(in[i+1])
i += 2
} else {
out.WriteByte(b)
i++
}
case CRLF_LF:
if i+1 < len(in) && ((b == '\r' && in[i+1] == '\n') || (b == '\n' && in[i+1] == '\r')) {
if isPair {
i += 2
} else {
i++
}
out.WriteByte('\n')
case CRLF_CR:
if i+1 < len(in) && ((b == '\r' && in[i+1] == '\n') || (b == '\n' && in[i+1] == '\r')) {
if isPair {
i += 2
} else {
i++
}
out.WriteByte('\r')
case CRLF_CRLF:
if i+1 < len(in) && ((b == '\r' && in[i+1] == '\n') || (b == '\n' && in[i+1] == '\r')) {
out.WriteString("\n")
if isPair {
i += 2
} else {
out.WriteString("\n")
i++
}
out.WriteString("\r\n") // (fixed to actually write CRLF)
case CRLF_LFCR:
if i+1 < len(in) && ((b == '\r' && in[i+1] == '\n') || (b == '\n' && in[i+1] == '\r')) {
out.WriteString("\r")
if isPair {
i += 2
} else {
out.WriteString("\r")
i++
}
out.WriteString("\n\r")
}
continue
}

View File

@ -33,7 +33,9 @@ interface SerialSettings {
enableEcho: boolean; // future use
normalizeMode: string; // future use
normalizeLineEnd: string; // future use
tabRender: string; // future use
preserveANSI: boolean; // future use
showNLTag: boolean; // future use
buttons: QuickButton[];
}
@ -56,7 +58,9 @@ export function SerialConsole() {
enableEcho: false,
normalizeMode: "names",
normalizeLineEnd: "keep",
tabRender: "",
preserveANSI: true,
showNLTag: true,
buttons: [],
});
@ -247,7 +251,7 @@ export function SerialConsole() {
{/* Serial settings (collapsible) */}
{!buttonConfig.hideSerialSettings && (
<>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-2 gap-4 mb-1">
<SelectMenuBasic
label="Baud Rate"
options={[
@ -365,6 +369,35 @@ export function SerialConsole() {
}}
/>
</div>
<div>
<SelectMenuBasic
className="mb-1"
label="Show newline tag"
options={[
{ label: "Hide <LF> tag", value: "hide" },
{ label: "Show <LF> tag", value: "show" },
]}
value={buttonConfig.showNLTag ? "show" : "hide"}
onChange={(e) => {
handleSerialSettingsChange("showNLTag", e.target.value === "show")
}}
/>
</div>
<div>
<InputFieldWithLabel
size="MD"
type="text"
label="Tab replacement"
placeholder="ex. spaces, →, |"
value={buttonConfig.tabRender}
onChange={e => {
handleSerialSettingsChange("tabRender", e.target.value)
}}
/>
<div className="text-xs text-white opacity-70 mt-1">
Empty for no replacement
</div>
</div>
</div>
<div className="space-y-4 m-2">
<SettingsItem