// 2024 - Christian Lee Seibold // License: MIT package main import ( "bufio" "fmt" "unicode" ) type TextParsingState struct { previousRune rune inStrong bool inEmphasis bool inMonospace bool } // Emphasis - one asterisk or one underscore // Strong - two asterisks (or two underscores) // Monospace - one backtick (`) // Nesting is not supported. Pass in a bool to not reset the state on EOF func (state *TextParsingState) print_markdown(reader *bufio.Reader, noEOFReset bool) { isBoundary := func(r rune) bool { return unicode.IsSpace(r) || unicode.IsPunct(r) } state.previousRune = '\n' for { r, _, err := reader.ReadRune() if err != nil { // EOF - reset everything, since this should be the end of the paragraph if !noEOFReset { fmt.Printf("\x1B[m") state.inStrong = false state.inEmphasis = false state.inMonospace = false } return } if r == '`' || (!state.inMonospace && (r == '*' || r == '_')) { toggleRune := r toggle := string(r) unread := true // Get the next rune r, _, err = reader.ReadRune() if err != nil { // Set rune to new line, so it registers as a whitespace. r = '\n' unread = false } // If r is an asterisk, we require two for the toggle, so read the next rune. Otherwise, if just one asterisk, // continue on as it's an italic. if toggleRune == '*' && r == '*' { toggle = "**" r, _, err = reader.ReadRune() if err != nil { // Set rune to new line, so it registers as a whitespace. r = '\n' unread = false } } else if toggleRune == '_' && r == '_' { toggle = "__" r, _, err = reader.ReadRune() if err != nil { // Set rune to new line, so it registers as a whitespace. r = '\n' unread = false } } if (isBoundary(state.previousRune) && !unicode.IsSpace(r) && r != toggleRune) || (!unicode.IsSpace(state.previousRune) && state.previousRune != toggleRune && isBoundary(r)) { switch toggleRune { case '`': if state.inMonospace { // Reset state.inMonospace = false fmt.Printf("\x1B[22m") // Since 22m disables bold *and* dim, set bold again if necessary if state.inStrong { fmt.Printf("\x1B[1m") } } else { // Set state.inMonospace = true fmt.Printf("\x1B[2m") } case '*': if toggle == "**" { // Bold if state.inStrong { // Reset state.inStrong = false // 22m disables bold *and* dim. However, we cannot use strong inside monospace toggles, so this doesn't matter. fmt.Printf("\x1B[22m") } else { // Set state.inStrong = true fmt.Printf("\x1B[1m") } } else { // Italics if state.inEmphasis { // Reset state.inEmphasis = false fmt.Printf("\x1B[23m") } else { // Set state.inEmphasis = true fmt.Printf("\x1B[3m") // Replace this with 4m for underline in terminals that don't support emphasis. } } case '_': if toggle == "__" { // Bold if state.inStrong { // Reset state.inStrong = false // 22m disables bold *and* dim. However, we cannot use strong inside monospace toggles, so this doesn't matter. fmt.Printf("\x1B[22m") } else { // Set state.inStrong = true fmt.Printf("\x1B[1m") } } else { // Italics if state.inEmphasis { // Reset state.inEmphasis = false fmt.Printf("\x1B[23m") } else { // Set state.inEmphasis = true fmt.Printf("\x1B[3m") // Replace this with 4m for underline in terminals that don't support emphasis. } } } _ = reader.UnreadRune() if unread { state.previousRune = toggleRune } } else { // Not a toggle, print the toggle and unread the rune fmt.Printf("%s", toggle) _ = reader.UnreadRune() state.previousRune = toggleRune } } else { fmt.Printf("%c", r) state.previousRune = r } } } // Emphasis - one underscore // Strong - two asterisks // Monospace - one backtick (`) // Pass in a bool to not reset the state on EOF func (state *TextParsingState) print_scroll(reader *bufio.Reader, noEOFReset bool) { isWhitespace := unicode.IsSpace state.previousRune = '\n' for { r, _, err := reader.ReadRune() if err != nil { // EOF - reset everything, since this should be the end of the paragraph if !noEOFReset { fmt.Printf("\x1B[m") state.inStrong = false state.inMonospace = false state.inEmphasis = false } return } if r == '`' || (!state.inMonospace && (r == '*' || r == '_')) { toggleRune := r toggle := string(r) unread := true // Get the next rune r, _, err = reader.ReadRune() if err != nil { // Set rune to space, so it registers as a whitespace. r = ' ' unread = false } // If r is an asterisk, we require two for the toggle, so read the next rune. Otherwise, if just one asterisk, // print the runes and continue if toggleRune == '*' && r == '*' { toggle = "**" r, _, err = reader.ReadRune() if err != nil { // Set rune to space, so it registers as a whitespace. r = ' ' unread = false } } else if toggleRune == '*' { fmt.Printf("*") _ = reader.UnreadRune() state.previousRune = '*' continue } if (!isWhitespace(r) && r != toggleRune) || (!isWhitespace(state.previousRune) && state.previousRune != toggleRune) { switch toggleRune { case '`': if state.inMonospace { // Reset state.inMonospace = false fmt.Printf("\x1B[22m") // Since 22m disables bold *and* dim, set bold again if necessary if state.inStrong { fmt.Printf("\x1B[1m") } } else { // Set state.inMonospace = true fmt.Printf("\x1B[2m") } case '*': if state.inStrong { // Reset state.inStrong = false // 22m disables bold *and* dim. However, we cannot use strong inside monospace toggles, so this doesn't matter. fmt.Printf("\x1B[22m") } else { // Set state.inStrong = true fmt.Printf("\x1B[1m") } case '_': if state.inEmphasis { // Reset state.inEmphasis = false fmt.Printf("\x1B[23m") } else { // Set state.inEmphasis = true fmt.Printf("\x1B[3m") // Replace this with 4m for underline in terminals that don't support emphasis. } } _ = reader.UnreadRune() if unread { state.previousRune = toggleRune } } else { // Not a toggle, print the toggle and unread the rune fmt.Printf("%s", toggle) _ = reader.UnreadRune() state.previousRune = toggleRune } } else { fmt.Printf("%c", r) state.previousRune = r } } } // Emphasis - one underscore // Strong - one asterisk // Monospace - one backtick (`) // Pass in a bool to not reset the state on EOF func (state *TextParsingState) print_asciidoc(reader *bufio.Reader, noEOFReset bool) { isWhitespace := unicode.IsSpace state.previousRune = '\n' for { r, _, err := reader.ReadRune() if err != nil { // EOF - reset everything, since this should be the end of the paragraph if !noEOFReset { fmt.Printf("\x1B[m") state.inStrong = false state.inMonospace = false state.inEmphasis = false } return } if r == '`' || (!state.inMonospace && (r == '*' || r == '_')) { toggleRune := r toggle := string(r) unread := true // Get the next rune r, _, err = reader.ReadRune() if err != nil { // Set rune to space, so it registers as a whitespace. r = ' ' unread = false } if (!isWhitespace(r) && r != toggleRune) || (!isWhitespace(state.previousRune) && state.previousRune != toggleRune) { switch toggleRune { case '`': if state.inMonospace { // Reset state.inMonospace = false fmt.Printf("\x1B[22m") // Since 22m disables bold *and* dim, set bold again if necessary if state.inStrong { fmt.Printf("\x1B[1m") } } else { // Set state.inMonospace = true fmt.Printf("\x1B[2m") } case '*': if state.inStrong { // Reset state.inStrong = false // 22m disables bold *and* dim. However, we cannot use strong inside monospace toggles, so this doesn't matter. fmt.Printf("\x1B[22m") } else { // Set state.inStrong = true fmt.Printf("\x1B[1m") } case '_': if state.inEmphasis { // Reset state.inEmphasis = false fmt.Printf("\x1B[23m") } else { // Set state.inEmphasis = true fmt.Printf("\x1B[3m") // Replace this with 4m for underline in terminals that don't support emphasis. } } _ = reader.UnreadRune() if unread { state.previousRune = toggleRune } } else { // Not a toggle, print the toggle and unread the rune fmt.Printf("%s", toggle) _ = reader.UnreadRune() state.previousRune = toggleRune } } else { fmt.Printf("%c", r) state.previousRune = r } } } gemini://auragem.letz.dev/devlog/terminal_emphasis_strong_scrollterm.go

-- Leo's gemini proxy

-- Connecting to auragem.letz.dev:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/plain; charset=utf-8; lang=en

-- Response ended

-- Page fetched on Sun May 19 09:27:10 2024