package notesium import ( "bufio" "fmt" "io" "os" "regexp" "strings" ) const ( ansiHeading = "\033[1m" ansiBold = "\033[1m" ansiItalic = "\033[3m" ansiLink = "\033[34m" ansiCodeBlock = "\033[33m" ansiInlineCode = "\033[33m" ansiBlockQuote = "\033[36m" ansiListMarker = "\033[36m" ansiReset = "\033[0m" ) var ansiLineBg = func() string { switch os.Getenv("NOTESIUM_FINDER_THEME") { case "light": return "\033[48;5;7m" default: return "\033[40m" } }() var ( reBold = regexp.MustCompile(`\*\*(.*?)\*\*`) reBoldAlt = regexp.MustCompile(`__(.*?)__`) reItalic = regexp.MustCompile(`\*(.*?)\*`) reItalicAlt = regexp.MustCompile(`_(.*?)_`) reUnorderedList = regexp.MustCompile(`^(\s*[-+*]) `) reOrderedList = regexp.MustCompile(`^(\s*\d+\.) `) reInlineCode = regexp.MustCompile("`(.*?)`") reLinkPlain = regexp.MustCompile(`(?:https?://|www\.)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/[^\s]*)?`) reLinkMarkdown = regexp.MustCompile(`\[(.[^]]*?)\]\((.[^)]*?)\)`) reAnsi = regexp.MustCompile(`\x1b\[[0-9;]*m`) reReset = regexp.MustCompile(`\x1b\[0m`) ) func renderMarkdown(reader io.Reader, writer io.Writer, lineNumber int) { inCodeBlock := false scanner := bufio.NewScanner(reader) for lineIndex := 1; scanner.Scan(); lineIndex++ { line := scanner.Text() highlightedLine := highlightLine(line, &inCodeBlock) if lineNumber > 0 && lineNumber == lineIndex { highlightedLine = highlightLineWithBackground(highlightedLine) } fmt.Fprintln(writer, highlightedLine) } if err := scanner.Err(); err != nil { fmt.Fprintf(writer, "Error reading content: %v\n", err) } } func highlightLine(line string, inCodeBlock *bool) string { // Code blocks if strings.HasPrefix(line, "```") { *inCodeBlock = !*inCodeBlock return ansiCodeBlock + line + ansiReset } if *inCodeBlock { return ansiCodeBlock + line + ansiReset } // Headers if strings.HasPrefix(line, "#") { return ansiHeading + line + ansiReset } // Blockquotes if strings.HasPrefix(line, "> ") { return ansiBlockQuote + line + ansiReset } // Inline code matches := reInlineCode.FindAllStringSubmatchIndex(line, -1) if len(matches) > 0 { return highlightLineWithInlineCode(line, matches) } return highlightString(line) } func highlightString(line string) string { // Links line = highlightLink(line, reLinkMarkdown, ansiLink) line = highlightRegex(line, reLinkPlain, ansiLink, 0) // Bold (**text** or __text__) line = highlightRegex(line, reBold, ansiBold, 2) line = highlightRegex(line, reBoldAlt, ansiBold, 2) // Italic (*text* or _text_) line = highlightRegex(line, reItalic, ansiItalic, 1) line = highlightRegex(line, reItalicAlt, ansiItalic, 1) // List markers line = highlightRegex(line, reUnorderedList, ansiListMarker, 0) line = highlightRegex(line, reOrderedList, ansiListMarker, 0) return line } func highlightRegex(line string, re *regexp.Regexp, ansiCode string, markerLength int) string { return re.ReplaceAllStringFunc(line, func(match string) string { inner := match[markerLength : len(match)-markerLength] return ansiCode + inner + ansiReset }) } func highlightLink(line string, re *regexp.Regexp, ansiCode string) string { return re.ReplaceAllStringFunc(line, func(match string) string { matches := re.FindStringSubmatch(match) if len(matches) >= 2 { title := matches[1] return ansiCode + title + ansiReset } return match }) } func highlightLineWithInlineCode(line string, matches [][]int) string { var builder strings.Builder prevIndex := 0 for _, match := range matches { start, end := match[0], match[1] groupStart, groupEnd := match[2], match[3] // Handle text before inline code if start > prevIndex { builder.WriteString(highlightString(line[prevIndex:start])) } builder.WriteString(ansiInlineCode) builder.WriteString(line[groupStart:groupEnd]) builder.WriteString(ansiReset) prevIndex = end } // Handle text after inline code if prevIndex < len(line) { builder.WriteString(highlightString(line[prevIndex:])) } return builder.String() } func highlightLineWithBackground(highlightedLine string) string { // apply bg after resets to handle segments highlightedLine = reReset.ReplaceAllStringFunc(highlightedLine, func(reset string) string { return reset + ansiLineBg }) // apply padding termWidth := 79 visibleChars := len(reAnsi.ReplaceAllString(highlightedLine, "")) requiredPadding := termWidth - visibleChars if requiredPadding > 0 { padding := strings.Repeat(" ", requiredPadding) highlightedLine += ansiLineBg + padding } return ansiLineBg + highlightedLine + ansiReset }