Fixed render
This commit is contained in:
parent
68b3d1834a
commit
5db905a12a
4 changed files with 85 additions and 29 deletions
|
|
@ -225,9 +225,11 @@ func (m *EditorModel) handleModeSwitch(key string) tea.Cmd {
|
||||||
switch key {
|
switch key {
|
||||||
case "i":
|
case "i":
|
||||||
m.mode = INSERT
|
m.mode = INSERT
|
||||||
|
m.insertUndoTaken = false
|
||||||
m.SetStatusMsg("")
|
m.SetStatusMsg("")
|
||||||
case "a":
|
case "a":
|
||||||
m.mode = INSERT
|
m.mode = INSERT
|
||||||
|
m.insertUndoTaken = false
|
||||||
line := []rune(m.lines[m.cursorY])
|
line := []rune(m.lines[m.cursorY])
|
||||||
if m.cursorX < len(line) {
|
if m.cursorX < len(line) {
|
||||||
m.cursorX++
|
m.cursorX++
|
||||||
|
|
@ -235,10 +237,12 @@ func (m *EditorModel) handleModeSwitch(key string) tea.Cmd {
|
||||||
m.SetStatusMsg("")
|
m.SetStatusMsg("")
|
||||||
case "A":
|
case "A":
|
||||||
m.mode = INSERT
|
m.mode = INSERT
|
||||||
|
m.insertUndoTaken = false
|
||||||
m.cursorX = len([]rune(m.lines[m.cursorY]))
|
m.cursorX = len([]rune(m.lines[m.cursorY]))
|
||||||
m.SetStatusMsg("")
|
m.SetStatusMsg("")
|
||||||
case "I":
|
case "I":
|
||||||
m.mode = INSERT
|
m.mode = INSERT
|
||||||
|
m.insertUndoTaken = false
|
||||||
m.cursorX = 0
|
m.cursorX = 0
|
||||||
m.SetStatusMsg("")
|
m.SetStatusMsg("")
|
||||||
case "o":
|
case "o":
|
||||||
|
|
@ -247,6 +251,7 @@ func (m *EditorModel) handleModeSwitch(key string) tea.Cmd {
|
||||||
m.cursorY++
|
m.cursorY++
|
||||||
m.cursorX = 0
|
m.cursorX = 0
|
||||||
m.mode = INSERT
|
m.mode = INSERT
|
||||||
|
m.insertUndoTaken = true
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
m.SetStatusMsg("")
|
m.SetStatusMsg("")
|
||||||
case "O":
|
case "O":
|
||||||
|
|
@ -254,6 +259,7 @@ func (m *EditorModel) handleModeSwitch(key string) tea.Cmd {
|
||||||
m.insertLine(m.cursorY)
|
m.insertLine(m.cursorY)
|
||||||
m.cursorX = 0
|
m.cursorX = 0
|
||||||
m.mode = INSERT
|
m.mode = INSERT
|
||||||
|
m.insertUndoTaken = true
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
m.SetStatusMsg("")
|
m.SetStatusMsg("")
|
||||||
default:
|
default:
|
||||||
|
|
@ -632,6 +638,7 @@ func (m *EditorModel) handleInsertMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
switch key {
|
switch key {
|
||||||
case "esc":
|
case "esc":
|
||||||
m.mode = NORMAL
|
m.mode = NORMAL
|
||||||
|
m.insertUndoTaken = false
|
||||||
line := []rune(m.lines[m.cursorY])
|
line := []rune(m.lines[m.cursorY])
|
||||||
if m.cursorX > 0 && m.cursorX >= len(line) {
|
if m.cursorX > 0 && m.cursorX >= len(line) {
|
||||||
m.cursorX = len(line) - 1
|
m.cursorX = len(line) - 1
|
||||||
|
|
@ -664,13 +671,13 @@ func (m *EditorModel) handleInsertMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
case "end":
|
case "end":
|
||||||
m.cursorX = len([]rune(m.lines[m.cursorY]))
|
m.cursorX = len([]rune(m.lines[m.cursorY]))
|
||||||
case "enter":
|
case "enter":
|
||||||
m.saveUndoSnapshot()
|
m.saveInsertUndoSnapshot()
|
||||||
m.splitLineRune(m.cursorY, m.cursorX)
|
m.splitLineRune(m.cursorY, m.cursorX)
|
||||||
m.cursorY++
|
m.cursorY++
|
||||||
m.cursorX = 0
|
m.cursorX = 0
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
case "backspace":
|
case "backspace":
|
||||||
m.saveUndoSnapshot()
|
m.saveInsertUndoSnapshot()
|
||||||
if m.cursorX > 0 {
|
if m.cursorX > 0 {
|
||||||
m.deleteCharAt(m.cursorY, m.cursorX-1)
|
m.deleteCharAt(m.cursorY, m.cursorX-1)
|
||||||
m.cursorX--
|
m.cursorX--
|
||||||
|
|
@ -684,7 +691,7 @@ func (m *EditorModel) handleInsertMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
}
|
}
|
||||||
case "delete":
|
case "delete":
|
||||||
m.saveUndoSnapshot()
|
m.saveInsertUndoSnapshot()
|
||||||
line := []rune(m.lines[m.cursorY])
|
line := []rune(m.lines[m.cursorY])
|
||||||
if m.cursorX < len(line) {
|
if m.cursorX < len(line) {
|
||||||
m.deleteCharAt(m.cursorY, m.cursorX)
|
m.deleteCharAt(m.cursorY, m.cursorX)
|
||||||
|
|
@ -695,7 +702,7 @@ func (m *EditorModel) handleInsertMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
}
|
}
|
||||||
case "tab":
|
case "tab":
|
||||||
m.saveUndoSnapshot()
|
m.saveInsertUndoSnapshot()
|
||||||
m.insertRuneAt(m.cursorY, m.cursorX, []rune(strings.Repeat(" ", m.tabWidth)))
|
m.insertRuneAt(m.cursorY, m.cursorX, []rune(strings.Repeat(" ", m.tabWidth)))
|
||||||
m.cursorX += m.tabWidth
|
m.cursorX += m.tabWidth
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
|
|
@ -703,7 +710,7 @@ func (m *EditorModel) handleInsertMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
runes := []rune(msg.String())
|
runes := []rune(msg.String())
|
||||||
if len(runes) == 1 && runes[0] >= 32 &&
|
if len(runes) == 1 && runes[0] >= 32 &&
|
||||||
(msg.Type == tea.KeyRunes || msg.Type == tea.KeySpace) {
|
(msg.Type == tea.KeyRunes || msg.Type == tea.KeySpace) {
|
||||||
m.saveUndoSnapshot()
|
m.saveInsertUndoSnapshot()
|
||||||
m.insertRuneAt(m.cursorY, m.cursorX, runes)
|
m.insertRuneAt(m.cursorY, m.cursorX, runes)
|
||||||
m.cursorX++
|
m.cursorX++
|
||||||
m.MarkDirty()
|
m.MarkDirty()
|
||||||
|
|
|
||||||
26
model.go
26
model.go
|
|
@ -90,6 +90,12 @@ type EditorModel struct {
|
||||||
// Undo/Redo
|
// Undo/Redo
|
||||||
undoStack []undoSnapshot
|
undoStack []undoSnapshot
|
||||||
redoStack []undoSnapshot
|
redoStack []undoSnapshot
|
||||||
|
// insertUndoTaken — снимок для undo уже сделан в текущей INSERT-сессии.
|
||||||
|
// Без этого saveUndoSnapshot() копировал весь []string на каждый
|
||||||
|
// введённый символ. Реальный vim тоже группирует набор текста между
|
||||||
|
// входом в INSERT и Esc в один undo-шаг — так что это одновременно
|
||||||
|
// и оптимизация, и исправление поведения под ожидаемое от vim.
|
||||||
|
insertUndoTaken bool
|
||||||
|
|
||||||
// Help экран
|
// Help экран
|
||||||
showHelp bool
|
showHelp bool
|
||||||
|
|
@ -277,6 +283,7 @@ func (m *EditorModel) openFile(path string) {
|
||||||
m.dirty = false
|
m.dirty = false
|
||||||
m.undoStack = nil
|
m.undoStack = nil
|
||||||
m.redoStack = nil
|
m.redoStack = nil
|
||||||
|
m.insertUndoTaken = false
|
||||||
m.searchMatches = nil
|
m.searchMatches = nil
|
||||||
m.numPrefix = ""
|
m.numPrefix = ""
|
||||||
m.pendingKeys = ""
|
m.pendingKeys = ""
|
||||||
|
|
@ -296,7 +303,11 @@ func (m *EditorModel) SetStatusMsg(msg string) { m.statusMsg = msg }
|
||||||
func (m *EditorModel) MarkDirty() {
|
func (m *EditorModel) MarkDirty() {
|
||||||
m.dirty = true
|
m.dirty = true
|
||||||
m.previewDirty = true
|
m.previewDirty = true
|
||||||
InvalidateHighlightCache()
|
// Кеш подсветки НЕ сбрасываем: ключ кеша уже включает содержимое строки
|
||||||
|
// (highlightCacheKey{line, fileType}), так что неизменившиеся строки
|
||||||
|
// остаются валидными в кеше сами по себе. Полный сброс на каждый
|
||||||
|
// keystroke означал, что все видимые строки перевычислялись заново
|
||||||
|
// после ввода одного символа — реальный источник лагов на больших файлах.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *EditorModel) CursorWithinBounds() {
|
func (m *EditorModel) CursorWithinBounds() {
|
||||||
|
|
@ -348,6 +359,17 @@ func (m *EditorModel) saveUndoSnapshot() {
|
||||||
m.redoStack = nil
|
m.redoStack = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveInsertUndoSnapshot берёт снимок для undo не чаще одного раза за
|
||||||
|
// INSERT-сессию (от входа в режим до Esc), а не на каждый введённый символ.
|
||||||
|
// Так делает и настоящий vim: набор текста целиком отменяется одним `u`.
|
||||||
|
func (m *EditorModel) saveInsertUndoSnapshot() {
|
||||||
|
if m.insertUndoTaken {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.saveUndoSnapshot()
|
||||||
|
m.insertUndoTaken = true
|
||||||
|
}
|
||||||
|
|
||||||
func (m *EditorModel) Undo() {
|
func (m *EditorModel) Undo() {
|
||||||
if len(m.undoStack) == 0 {
|
if len(m.undoStack) == 0 {
|
||||||
m.SetStatusMsg("Already at oldest change")
|
m.SetStatusMsg("Already at oldest change")
|
||||||
|
|
@ -369,7 +391,6 @@ func (m *EditorModel) Undo() {
|
||||||
m.CursorWithinBounds()
|
m.CursorWithinBounds()
|
||||||
m.dirty = true
|
m.dirty = true
|
||||||
m.previewDirty = true
|
m.previewDirty = true
|
||||||
InvalidateHighlightCache()
|
|
||||||
m.SetStatusMsg("Undo")
|
m.SetStatusMsg("Undo")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,6 +416,5 @@ func (m *EditorModel) Redo() {
|
||||||
m.CursorWithinBounds()
|
m.CursorWithinBounds()
|
||||||
m.dirty = true
|
m.dirty = true
|
||||||
m.previewDirty = true
|
m.previewDirty = true
|
||||||
InvalidateHighlightCache()
|
|
||||||
m.SetStatusMsg("Redo")
|
m.SetStatusMsg("Redo")
|
||||||
}
|
}
|
||||||
53
render.go
53
render.go
|
|
@ -19,6 +19,26 @@ var (
|
||||||
vwCacheMax = 4000
|
vwCacheMax = 4000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// === Скомпилированные regex для markdown ===
|
||||||
|
// Компилируются один раз при старте программы вместо повторной компиляции
|
||||||
|
// на каждый вызов рендера (который происходит на каждый keystroke при
|
||||||
|
// открытом live preview). regexp.Compile — это не просто аллокация, а
|
||||||
|
// разбор паттерна в NFA, повторение этого на каждую строку документа на
|
||||||
|
// каждое нажатие клавиши создавало заметную нагрузку на больших файлах.
|
||||||
|
var (
|
||||||
|
reMdHeadingLoose = regexp.MustCompile(`^(#+)\s+(.*)`)
|
||||||
|
reMdBold = regexp.MustCompile(`\*\*([^*]+)\*\*`)
|
||||||
|
reMdItalic = regexp.MustCompile(`\*([^*]+)\*`)
|
||||||
|
reMdInlineCode = regexp.MustCompile("`([^`]+)`")
|
||||||
|
reMdLink = regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`)
|
||||||
|
reMdLinkText = regexp.MustCompile(`\[([^\]]+)\]`)
|
||||||
|
reMdHR = regexp.MustCompile(`^(-{3,}|_{3,}|\*{3,})$`)
|
||||||
|
reMdHeadingStrict = regexp.MustCompile(`^(#{1,4})\s+(.+)`)
|
||||||
|
reMdBullet = regexp.MustCompile(`^(\s*)([-*+])\s+(.+)`)
|
||||||
|
reMdOrdered = regexp.MustCompile(`^(\s*)(\d+)\.\s+(.+)`)
|
||||||
|
reMdTableSep = regexp.MustCompile(`^\|[\s\-:|]+\|`)
|
||||||
|
)
|
||||||
|
|
||||||
// === UI стили ===
|
// === UI стили ===
|
||||||
var (
|
var (
|
||||||
colorBold = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("15"))
|
colorBold = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("15"))
|
||||||
|
|
@ -460,8 +480,7 @@ func truncateANSI(s string, maxWidth int) string {
|
||||||
// === Markdown ===
|
// === Markdown ===
|
||||||
|
|
||||||
func colorizeMarkdown(line string) string {
|
func colorizeMarkdown(line string) string {
|
||||||
headingRe := regexp.MustCompile(`^(#+)\s+(.*)`)
|
if m := reMdHeadingLoose.FindStringSubmatch(line); m != nil {
|
||||||
if m := headingRe.FindStringSubmatch(line); m != nil {
|
|
||||||
level := len(m[1])
|
level := len(m[1])
|
||||||
text := m[2]
|
text := m[2]
|
||||||
switch level {
|
switch level {
|
||||||
|
|
@ -475,17 +494,17 @@ func colorizeMarkdown(line string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
result := line
|
result := line
|
||||||
result = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdBold.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
return colorBold.Render(s[2 : len(s)-2])
|
return colorBold.Render(s[2 : len(s)-2])
|
||||||
})
|
})
|
||||||
result = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdItalic.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
return lipgloss.NewStyle().Italic(true).Render(s[1 : len(s)-1])
|
return lipgloss.NewStyle().Italic(true).Render(s[1 : len(s)-1])
|
||||||
})
|
})
|
||||||
result = regexp.MustCompile("`([^`]+)`").ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdInlineCode.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
return colorCode.Render(s[1 : len(s)-1])
|
return colorCode.Render(s[1 : len(s)-1])
|
||||||
})
|
})
|
||||||
result = regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`).ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdLink.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
m := regexp.MustCompile(`\[([^\]]+)\]`).FindStringSubmatch(s)
|
m := reMdLinkText.FindStringSubmatch(s)
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return colorLink.Render(m[1])
|
return colorLink.Render(m[1])
|
||||||
}
|
}
|
||||||
|
|
@ -540,10 +559,10 @@ func renderMarkdown(lines []string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderMarkdownLine(line string) string {
|
func renderMarkdownLine(line string) string {
|
||||||
if regexp.MustCompile(`^(-{3,}|_{3,}|\*{3,})$`).MatchString(strings.TrimSpace(line)) {
|
if reMdHR.MatchString(strings.TrimSpace(line)) {
|
||||||
return colorHR.Render(strings.Repeat("─", 60))
|
return colorHR.Render(strings.Repeat("─", 60))
|
||||||
}
|
}
|
||||||
if m := regexp.MustCompile(`^(#{1,4})\s+(.+)`).FindStringSubmatch(line); m != nil {
|
if m := reMdHeadingStrict.FindStringSubmatch(line); m != nil {
|
||||||
level := len(m[1])
|
level := len(m[1])
|
||||||
text := applyInlineMarkdown(m[2])
|
text := applyInlineMarkdown(m[2])
|
||||||
switch level {
|
switch level {
|
||||||
|
|
@ -560,14 +579,14 @@ func renderMarkdownLine(line string) string {
|
||||||
if strings.HasPrefix(line, "> ") {
|
if strings.HasPrefix(line, "> ") {
|
||||||
return colorQuote.Render("┃ " + applyInlineMarkdown(strings.TrimPrefix(line, "> ")))
|
return colorQuote.Render("┃ " + applyInlineMarkdown(strings.TrimPrefix(line, "> ")))
|
||||||
}
|
}
|
||||||
if m := regexp.MustCompile(`^(\s*)([-*+])\s+(.+)`).FindStringSubmatch(line); m != nil {
|
if m := reMdBullet.FindStringSubmatch(line); m != nil {
|
||||||
return m[1] + colorListBul.Render("•") + " " + applyInlineMarkdown(m[3])
|
return m[1] + colorListBul.Render("•") + " " + applyInlineMarkdown(m[3])
|
||||||
}
|
}
|
||||||
if m := regexp.MustCompile(`^(\s*)(\d+)\.\s+(.+)`).FindStringSubmatch(line); m != nil {
|
if m := reMdOrdered.FindStringSubmatch(line); m != nil {
|
||||||
return m[1] + colorListBul.Render(m[2]+".") + " " + applyInlineMarkdown(m[3])
|
return m[1] + colorListBul.Render(m[2]+".") + " " + applyInlineMarkdown(m[3])
|
||||||
}
|
}
|
||||||
if strings.Contains(line, "|") && strings.HasPrefix(strings.TrimSpace(line), "|") {
|
if strings.Contains(line, "|") && strings.HasPrefix(strings.TrimSpace(line), "|") {
|
||||||
if regexp.MustCompile(`^\|[\s\-:|]+\|`).MatchString(strings.TrimSpace(line)) {
|
if reMdTableSep.MatchString(strings.TrimSpace(line)) {
|
||||||
return colorHR.Render(strings.Repeat("─", 40))
|
return colorHR.Render(strings.Repeat("─", 40))
|
||||||
}
|
}
|
||||||
cells := strings.Split(strings.Trim(strings.TrimSpace(line), "|"), "|")
|
cells := strings.Split(strings.Trim(strings.TrimSpace(line), "|"), "|")
|
||||||
|
|
@ -585,17 +604,17 @@ func renderMarkdownLine(line string) string {
|
||||||
|
|
||||||
func applyInlineMarkdown(text string) string {
|
func applyInlineMarkdown(text string) string {
|
||||||
result := text
|
result := text
|
||||||
result = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdBold.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
return colorBold.Render(s[2 : len(s)-2])
|
return colorBold.Render(s[2 : len(s)-2])
|
||||||
})
|
})
|
||||||
result = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdItalic.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
return lipgloss.NewStyle().Italic(true).Render(s[1 : len(s)-1])
|
return lipgloss.NewStyle().Italic(true).Render(s[1 : len(s)-1])
|
||||||
})
|
})
|
||||||
result = regexp.MustCompile("`([^`]+)`").ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdInlineCode.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
return colorCode.Render(s[1 : len(s)-1])
|
return colorCode.Render(s[1 : len(s)-1])
|
||||||
})
|
})
|
||||||
result = regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`).ReplaceAllStringFunc(result, func(s string) string {
|
result = reMdLink.ReplaceAllStringFunc(result, func(s string) string {
|
||||||
m := regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`).FindStringSubmatch(s)
|
m := reMdLink.FindStringSubmatch(s)
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return colorLink.Render(m[1]) + lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render(" ("+m[2]+")")
|
return colorLink.Render(m[1]) + lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render(" ("+m[2]+")")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
syntax.go
18
syntax.go
|
|
@ -193,17 +193,27 @@ func HighlightLine(line, fileType string) string {
|
||||||
tokens := tokenizeWithPatterns(line, cfg, patterns)
|
tokens := tokenizeWithPatterns(line, cfg, patterns)
|
||||||
result := renderTokens(tokens, cfg)
|
result := renderTokens(tokens, cfg)
|
||||||
|
|
||||||
// Сохраняем в кеш
|
// Сохраняем в кеш. Раньше при достижении highlightCacheMax кеш просто
|
||||||
|
// переставал принимать новые записи навсегда (пока кто-то не вызовет
|
||||||
|
// InvalidateHighlightCache вручную) — в паре с прошлым поведением, когда
|
||||||
|
// кеш сбрасывался целиком на каждый keystroke, это было не страшно, кеш
|
||||||
|
// никогда не успевал заполниться. Теперь кеш живёт долго, поэтому при
|
||||||
|
// заполнении просто освобождаем место под свежие строки.
|
||||||
highlightCacheMu.Lock()
|
highlightCacheMu.Lock()
|
||||||
if len(highlightCache) < highlightCacheMax {
|
if len(highlightCache) >= highlightCacheMax {
|
||||||
highlightCache[key] = result
|
highlightCache = map[highlightCacheKey]string{}
|
||||||
}
|
}
|
||||||
|
highlightCache[key] = result
|
||||||
highlightCacheMu.Unlock()
|
highlightCacheMu.Unlock()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidateHighlightCache сбрасывает кеш подсветки (вызывается при изменении файла)
|
// InvalidateHighlightCache сбрасывает кеш подсветки целиком. Кеш ключуется
|
||||||
|
// по (содержимое строки, тип файла), поэтому корректность не требует сброса
|
||||||
|
// на каждое изменение файла — неизменившиеся строки остаются валидными сами
|
||||||
|
// по себе. Эта функция оставлена как ручной сброс на случай, если появится
|
||||||
|
// сценарий, где это реально нужно (например смена цветовой темы на лету).
|
||||||
func InvalidateHighlightCache() {
|
func InvalidateHighlightCache() {
|
||||||
highlightCacheMu.Lock()
|
highlightCacheMu.Lock()
|
||||||
highlightCache = map[highlightCacheKey]string{}
|
highlightCache = map[highlightCacheKey]string{}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue