💾 Archived View for auragem.letz.dev › devlog › terminal_emphasis_strong_scrollterm.go captured on 2024-07-09 at 00:28:50.

View Raw

More Information

⬅️ Previous capture (2024-05-10)

-=-=-=-=-=-=-

// 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
		}
	}
}