523 lines
17 KiB
Go
523 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/x/ansi"
|
|
)
|
|
|
|
type memoryTextClipboard struct {
|
|
text string
|
|
written string
|
|
readErr error
|
|
writeErr error
|
|
}
|
|
|
|
func TestVimEditorUsesSharedConfiguredNavigation(t *testing.T) {
|
|
editor := newTextEditor("first\nsecond", true)
|
|
editor.keys.Navigation.Down = []string{"ctrl+j"}
|
|
editor.keys.Navigation.Up = []string{"ctrl+k"}
|
|
|
|
editor.handleKey(runeKey("j"), true)
|
|
if editor.Cursor != 0 {
|
|
t.Fatalf("removed default j moved cursor to %d", editor.Cursor)
|
|
}
|
|
editor.handleKey(tea.KeyMsg{Type: tea.KeyCtrlJ}, true)
|
|
if editor.Cursor != len([]rune("first\n")) {
|
|
t.Fatalf("configured ctrl+j moved cursor to %d", editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func (c *memoryTextClipboard) ReadText() (string, error) {
|
|
return c.text, c.readErr
|
|
}
|
|
|
|
func (c *memoryTextClipboard) WriteText(value string) error {
|
|
c.written = value
|
|
return c.writeErr
|
|
}
|
|
|
|
func TestVimTextEditorWordMotions(t *testing.T) {
|
|
const value = "one,two THREE four\nlast"
|
|
editor := newTextEditor(value, true)
|
|
|
|
editor.handleKey(runeKey("e"), true)
|
|
if editor.Cursor != 2 {
|
|
t.Fatalf("e cursor = %d, want 2", editor.Cursor)
|
|
}
|
|
editor.Cursor = 0
|
|
editor.handleKey(runeKey("E"), true)
|
|
if editor.Cursor != 6 {
|
|
t.Fatalf("E cursor = %d, want 6", editor.Cursor)
|
|
}
|
|
editor.Cursor = 0
|
|
editor.handleKey(runeKey("w"), true)
|
|
if editor.Cursor != 3 {
|
|
t.Fatalf("w cursor = %d, want punctuation at 3", editor.Cursor)
|
|
}
|
|
editor.Cursor = 0
|
|
editor.handleKey(runeKey("W"), true)
|
|
if editor.Cursor != 9 {
|
|
t.Fatalf("W cursor = %d, want 9", editor.Cursor)
|
|
}
|
|
editor.Cursor = 18
|
|
editor.handleKey(runeKey("b"), true)
|
|
if editor.Cursor != 15 {
|
|
t.Fatalf("b cursor = %d, want 15", editor.Cursor)
|
|
}
|
|
editor.Cursor = 18
|
|
editor.handleKey(runeKey("B"), true)
|
|
if editor.Cursor != 15 {
|
|
t.Fatalf("B cursor = %d, want 15", editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorWordEndMotionsRepeat(t *testing.T) {
|
|
const value = "one,two THREE four\nlast"
|
|
editor := newTextEditor(value, true)
|
|
for index, want := range []int{2, 3, 6, 13, 18, 23} {
|
|
editor.handleKey(runeKey("e"), true)
|
|
if editor.Cursor != want {
|
|
t.Fatalf("e repetition %d cursor = %d, want %d", index+1, editor.Cursor, want)
|
|
}
|
|
}
|
|
|
|
editor.Cursor = 0
|
|
for index, want := range []int{6, 13, 18, 23} {
|
|
editor.handleKey(runeKey("E"), true)
|
|
if editor.Cursor != want {
|
|
t.Fatalf("E repetition %d cursor = %d, want %d", index+1, editor.Cursor, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorWordEndRepeatsAcrossSoftWraps(t *testing.T) {
|
|
editor := newTextEditor("abcdefghijklmnopqrstuv", true)
|
|
for index, want := range []int{9, 19, 21} {
|
|
editor.handleKeyAtWidth(runeKey("e"), true, 10)
|
|
if editor.Cursor != want {
|
|
t.Fatalf("soft-wrap e repetition %d cursor = %d, want %d", index+1, editor.Cursor, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorFindAndRepeat(t *testing.T) {
|
|
editor := newTextEditor("foo bar foo", true)
|
|
editor.handleKey(runeKey("f"), true)
|
|
editor.handleKey(runeKey("o"), true)
|
|
if editor.Cursor != 1 {
|
|
t.Fatalf("fo cursor = %d, want 1", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey(";"), true)
|
|
if editor.Cursor != 2 {
|
|
t.Fatalf("first ; cursor = %d, want 2", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey(";"), true)
|
|
if editor.Cursor != 9 {
|
|
t.Fatalf("second ; cursor = %d, want 9", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey(","), true)
|
|
if editor.Cursor != 2 {
|
|
t.Fatalf(", cursor = %d, want 2", editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorTillRepeatAdvancesPastPreviousTarget(t *testing.T) {
|
|
editor := newTextEditor("xxa-b-c-d", true)
|
|
editor.handleKey(runeKey("t"), true)
|
|
editor.handleKey(runeKey("-"), true)
|
|
if editor.Cursor != 2 {
|
|
t.Fatalf("t- cursor = %d, want 2", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey(";"), true)
|
|
if editor.Cursor != 4 {
|
|
t.Fatalf("; cursor = %d, want 4", editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorSwitchesModesAndInserts(t *testing.T) {
|
|
editor := newTextEditor("task", true)
|
|
if editor.Mode != textEditorNormal {
|
|
t.Fatalf("initial mode = %s", editor.Mode)
|
|
}
|
|
editor.handleKey(runeKey("i"), true)
|
|
editor.handleKey(runeKey("x"), true)
|
|
if editor.Text != "xtask" || editor.Mode != textEditorInsert {
|
|
t.Fatalf("insert result = %q mode=%s", editor.Text, editor.Mode)
|
|
}
|
|
editor.handleKey(tea.KeyMsg{Type: tea.KeyEsc}, true)
|
|
if editor.Mode != textEditorNormal {
|
|
t.Fatalf("escape mode = %s", editor.Mode)
|
|
}
|
|
if editor.Cursor != 0 {
|
|
t.Fatalf("escape cursor = %d, want 0", editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorSubstituteDeletesCharacterAndEntersInsert(t *testing.T) {
|
|
editor := newTextEditor("abc", true)
|
|
editor.Cursor = 1
|
|
editor.handleKey(runeKey("s"), true)
|
|
if editor.Text != "ac" || editor.Cursor != 1 || editor.Mode != textEditorInsert {
|
|
t.Fatalf("substitute result = %#v", editor)
|
|
}
|
|
editor.handleKey(runeKey("X"), true)
|
|
if editor.Text != "aXc" {
|
|
t.Fatalf("substitute insertion result = %q", editor.Text)
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorNormalMotionsStayOnCharactersWithinLine(t *testing.T) {
|
|
editor := newTextEditor("ab\n cd\n", true)
|
|
editor.Cursor = 1
|
|
editor.handleKey(runeKey("l"), true)
|
|
if editor.Cursor != 1 {
|
|
t.Fatalf("l crossed line at cursor %d", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey("x"), true)
|
|
if editor.Text != "a\n cd\n" {
|
|
t.Fatalf("x result = %q", editor.Text)
|
|
}
|
|
editor.handleKey(runeKey("x"), true)
|
|
if editor.Text != "a\n cd\n" {
|
|
t.Fatalf("x deleted newline: %q", editor.Text)
|
|
}
|
|
|
|
editor.Cursor = 4
|
|
editor.handleKey(runeKey("j"), true)
|
|
if editor.Cursor != 7 {
|
|
t.Fatalf("j cursor = %d, want empty last line at 7", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey("h"), true)
|
|
if editor.Cursor != 7 {
|
|
t.Fatalf("h crossed from empty line at cursor %d", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey("X"), true)
|
|
if editor.Text != "a\n cd\n" {
|
|
t.Fatalf("X deleted newline: %q", editor.Text)
|
|
}
|
|
}
|
|
|
|
func TestVimTextEditorDocumentMotionsUseFirstNonBlank(t *testing.T) {
|
|
editor := newTextEditor(" first\n last", true)
|
|
editor.Cursor = 10
|
|
editor.handleKey(runeKey("g"), true)
|
|
editor.handleKey(runeKey("g"), true)
|
|
if editor.Cursor != 2 {
|
|
t.Fatalf("gg cursor = %d, want 2", editor.Cursor)
|
|
}
|
|
editor.handleKey(runeKey("G"), true)
|
|
if editor.Cursor != 11 {
|
|
t.Fatalf("G cursor = %d, want 11", editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestEditorHighlightsCurrentLineWithoutChangingLayout(t *testing.T) {
|
|
editor := newTextEditor("short\nsecond", true)
|
|
lines := renderTextEditor(editor, 20, true)
|
|
if len(lines) != 2 || strings.Contains(joinEditorLines(lines), "█") {
|
|
t.Fatalf("cursor changed editor layout: %#v", lines)
|
|
}
|
|
if width := ansi.StringWidth(lines[0].text); width != 20 {
|
|
t.Fatalf("active line width = %d, want 20", width)
|
|
}
|
|
if width := ansi.StringWidth(lines[1].text); width != len("second") {
|
|
t.Fatalf("inactive line width = %d", width)
|
|
}
|
|
if !lines[0].active || lines[1].active {
|
|
t.Fatalf("active rows = %#v", lines)
|
|
}
|
|
|
|
editor.handleKey(runeKey("j"), true)
|
|
lines = renderTextEditor(editor, 20, true)
|
|
if width := ansi.StringWidth(lines[0].text); width != len("short") {
|
|
t.Fatalf("old line remained highlighted at width %d", width)
|
|
}
|
|
if width := ansi.StringWidth(lines[1].text); width != 20 {
|
|
t.Fatalf("new active line width = %d, want 20", width)
|
|
}
|
|
}
|
|
|
|
func TestEditorKeepsWrappedRowsAndContextRailsVisible(t *testing.T) {
|
|
editor := newTextEditor("abcdefghijklmnopqrstuv", true)
|
|
editor.Cursor = 16
|
|
rendered := renderTextEditor(editor, 10, true)
|
|
if len(rendered) != 3 {
|
|
t.Fatalf("wrapped rows = %d, want 3", len(rendered))
|
|
}
|
|
|
|
app := App{prEditField: prEditBodyField}
|
|
app.prEditEditors[prEditBodyField] = editor
|
|
rows := app.prEditFieldLines("description", prEditBodyField, 14)
|
|
if len(rows) != 4 {
|
|
t.Fatalf("field rows = %#v", rows)
|
|
}
|
|
want := []string{"│ abcdefghij", "│ klmnopqrst", "│ uv"}
|
|
for index, expected := range want {
|
|
plain := strings.TrimRight(ansi.Strip(rows[index+1]), " ")
|
|
if plain != expected {
|
|
t.Fatalf("wrapped row %d = %q, want %q", index, plain, expected)
|
|
}
|
|
if ansi.StringWidth(rows[index+1]) > 12 {
|
|
t.Fatalf("wrapped row %d is too wide: %d", index, ansi.StringWidth(rows[index+1]))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVimEditorTreatsSoftWrapsAsVisualLinesWithoutChangingText(t *testing.T) {
|
|
const value = "abcdefghijklmnopqrstuv"
|
|
editor := newTextEditor(value, true)
|
|
editor.Cursor = 2
|
|
|
|
editor.handleKeyAtWidth(runeKey("j"), true, 10)
|
|
if editor.Cursor != 12 {
|
|
t.Fatalf("first visual j cursor = %d, want 12", editor.Cursor)
|
|
}
|
|
editor.handleKeyAtWidth(runeKey("$"), true, 10)
|
|
if editor.Cursor != 19 {
|
|
t.Fatalf("visual $ cursor = %d, want 19", editor.Cursor)
|
|
}
|
|
editor.handleKeyAtWidth(runeKey("l"), true, 10)
|
|
if editor.Cursor != 19 {
|
|
t.Fatalf("l crossed soft wrap at cursor %d", editor.Cursor)
|
|
}
|
|
editor.handleKeyAtWidth(runeKey("j"), true, 10)
|
|
if editor.Cursor != 21 {
|
|
t.Fatalf("second visual j cursor = %d, want 21", editor.Cursor)
|
|
}
|
|
editor.handleKeyAtWidth(runeKey("0"), true, 10)
|
|
if editor.Cursor != 20 {
|
|
t.Fatalf("visual 0 cursor = %d, want 20", editor.Cursor)
|
|
}
|
|
editor.handleKeyAtWidth(runeKey("k"), true, 10)
|
|
if editor.Cursor != 10 {
|
|
t.Fatalf("visual k cursor = %d, want 10", editor.Cursor)
|
|
}
|
|
if editor.Text != value {
|
|
t.Fatalf("visual navigation changed stored text: %q", editor.Text)
|
|
}
|
|
|
|
rendered := renderTextEditor(editor, 10, true)
|
|
activeRows := 0
|
|
for _, line := range rendered {
|
|
if line.active {
|
|
activeRows++
|
|
}
|
|
}
|
|
if activeRows != 1 {
|
|
t.Fatalf("active visual rows = %d, want 1", activeRows)
|
|
}
|
|
}
|
|
|
|
func TestEditorDoesNotAddPhantomRowAtExactSoftWrap(t *testing.T) {
|
|
editor := newTextEditor("abcdefghijklmnopqrst", true)
|
|
editor.Cursor = len([]rune(editor.Text))
|
|
rendered := renderTextEditor(editor, 10, true)
|
|
if len(rendered) != 2 {
|
|
t.Fatalf("rendered rows = %d, want 2: %#v", len(rendered), rendered)
|
|
}
|
|
if !rendered[1].active {
|
|
t.Fatalf("last wrapped row is not active: %#v", rendered)
|
|
}
|
|
}
|
|
|
|
func TestEditorLineEndingNormalizationRemovesTerminalCarriageReturns(t *testing.T) {
|
|
const mixed = "first\nsecond\r\nthird\rfourth"
|
|
normalized := normalizeLineEndings(mixed)
|
|
if normalized != "first\nsecond\nthird\nfourth" {
|
|
t.Fatalf("normalized text = %q", normalized)
|
|
}
|
|
editor := newTextEditor(normalized, true)
|
|
for _, line := range renderTextEditor(editor, 80, true) {
|
|
if strings.ContainsRune(line.text, '\r') {
|
|
t.Fatalf("rendered terminal carriage return in %#v", line)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVimVisualModeDeletesAcrossSoftWrappedRows(t *testing.T) {
|
|
editor := newTextEditor("abcdefghijklmnopqrstuv", true)
|
|
editor.Cursor = 2
|
|
editor.handleKeyAtWidth(runeKey("v"), true, 10)
|
|
editor.handleKeyAtWidth(runeKey("j"), true, 10)
|
|
editor.handleKeyAtWidth(runeKey("l"), true, 10)
|
|
if editor.Mode != textEditorVisual || editor.Cursor != 13 {
|
|
t.Fatalf("visual selection mode=%s cursor=%d", editor.Mode, editor.Cursor)
|
|
}
|
|
editor.handleKeyAtWidth(runeKey("d"), true, 10)
|
|
if editor.Text != "abopqrstuv" {
|
|
t.Fatalf("visual delete result = %q", editor.Text)
|
|
}
|
|
if editor.Mode != textEditorNormal || editor.Cursor != 2 {
|
|
t.Fatalf("after visual delete mode=%s cursor=%d", editor.Mode, editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimVisualYankAndPasteUseSystemClipboardAbstraction(t *testing.T) {
|
|
clipboard := &memoryTextClipboard{}
|
|
editor := newTextEditor("abcdef", true)
|
|
editor.clipboard = clipboard
|
|
editor.handleKey(runeKey("v"), true)
|
|
editor.handleKey(runeKey("l"), true)
|
|
editor.handleKey(runeKey("l"), true)
|
|
editor.handleKey(runeKey("y"), true)
|
|
if clipboard.written != "abc" {
|
|
t.Fatalf("yanked text = %q, want abc", clipboard.written)
|
|
}
|
|
if editor.Text != "abcdef" || editor.Mode != textEditorNormal {
|
|
t.Fatalf("yank changed editor: %#v", editor)
|
|
}
|
|
|
|
clipboard.text = "XY"
|
|
editor.Cursor = 0
|
|
editor.handleKey(runeKey("p"), true)
|
|
if editor.Text != "aXYbcdef" || editor.Cursor != 2 {
|
|
t.Fatalf("paste result text=%q cursor=%d", editor.Text, editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimVisualLineYankCollapsesSoftWraps(t *testing.T) {
|
|
clipboard := &memoryTextClipboard{}
|
|
editor := newTextEditor("abcdefghijklmnopqrstuv", true)
|
|
editor.clipboard = clipboard
|
|
editor.Cursor = 2
|
|
editor.handleKeyAtWidth(runeKey("V"), true, 10)
|
|
editor.handleKeyAtWidth(runeKey("j"), true, 10)
|
|
editor.handleKeyAtWidth(runeKey("y"), true, 10)
|
|
if clipboard.written != "abcdefghijklmnopqrst" {
|
|
t.Fatalf("linewise soft-wrap yank = %q", clipboard.written)
|
|
}
|
|
if strings.ContainsRune(clipboard.written, '\n') {
|
|
t.Fatalf("soft-wrap yank introduced newline: %q", clipboard.written)
|
|
}
|
|
}
|
|
|
|
func TestVimVisualFindAcceptsArbitraryTarget(t *testing.T) {
|
|
editor := newTextEditor("one x two", true)
|
|
editor.handleKey(runeKey("v"), true)
|
|
editor.handleKey(runeKey("f"), true)
|
|
editor.handleKey(runeKey("x"), true)
|
|
if editor.Mode != textEditorVisual || editor.Cursor != 4 {
|
|
t.Fatalf("visual fx mode=%s cursor=%d", editor.Mode, editor.Cursor)
|
|
}
|
|
}
|
|
|
|
func TestVimClipboardErrorsRemainVisibleAndPreserveSelection(t *testing.T) {
|
|
clipboard := &memoryTextClipboard{writeErr: errors.New("clipboard failed")}
|
|
editor := newTextEditor("abc", true)
|
|
editor.clipboard = clipboard
|
|
editor.handleKey(runeKey("v"), true)
|
|
editor.handleKey(runeKey("y"), true)
|
|
if editor.err == nil || !strings.Contains(editor.err.Error(), "clipboard failed") {
|
|
t.Fatalf("clipboard error = %v", editor.err)
|
|
}
|
|
if editor.Mode != textEditorVisual || editor.Text != "abc" {
|
|
t.Fatalf("failed yank changed selection: %#v", editor)
|
|
}
|
|
}
|
|
|
|
func TestEditorRendersModeSpecificCursorsAndVisualSelection(t *testing.T) {
|
|
editor := newTextEditor("abc", true)
|
|
normal := renderTextEditor(editor, 10, true)
|
|
if !strings.Contains(normal[0].text, "\x1b[7m") || ansi.Strip(normal[0].text) != "abc " {
|
|
t.Fatalf("normal cursor rendering = %q", normal[0].text)
|
|
}
|
|
|
|
editor.handleKey(runeKey("i"), true)
|
|
insert := renderTextEditor(editor, 10, true)
|
|
if !strings.Contains(insert[0].text, "\x1b[4m") {
|
|
t.Fatalf("insert cursor rendering = %q", insert[0].text)
|
|
}
|
|
if width := ansi.StringWidth(insert[0].text); width != 10 {
|
|
t.Fatalf("insert cursor changed row width to %d", width)
|
|
}
|
|
if plain := strings.TrimRight(ansi.Strip(insert[0].text), " "); plain != "abc" {
|
|
t.Fatalf("insert cursor hid or shifted text: %q", plain)
|
|
}
|
|
|
|
editor.handleKey(tea.KeyMsg{Type: tea.KeyEsc}, true)
|
|
editor.handleKey(runeKey("v"), true)
|
|
editor.handleKey(runeKey("l"), true)
|
|
visual := renderTextEditor(editor, 10, true)
|
|
if editor.modeLabel() != "VISUAL" || !strings.Contains(visual[0].text, "\x1b[7m") {
|
|
t.Fatalf("visual rendering mode=%s text=%q", editor.modeLabel(), visual[0].text)
|
|
}
|
|
}
|
|
|
|
func TestHardwareInsertCursorDoesNotAlterRenderedText(t *testing.T) {
|
|
editor := newTextEditor("abc", true)
|
|
editor.hardwareCursor = true
|
|
editor.handleKey(runeKey("i"), true)
|
|
rendered := renderTextEditor(editor, 10, true)
|
|
if plain := strings.TrimRight(ansi.Strip(rendered[0].text), " "); plain != "abc" {
|
|
t.Fatalf("hardware cursor altered text: %q", plain)
|
|
}
|
|
if strings.Contains(rendered[0].text, "\x1b[4m") {
|
|
t.Fatalf("hardware cursor retained fallback underline: %q", rendered[0].text)
|
|
}
|
|
}
|
|
|
|
func TestMarkdownHighlightingPreservesTextWidthsAndCursorIndexes(t *testing.T) {
|
|
const markdown = "# Heading\nUse `code` and [link](https://example.com)."
|
|
editor := newTextEditor(markdown, true)
|
|
editor.highlightMarkdown = true
|
|
rendered := renderTextEditor(editor, 80, false)
|
|
var lines []string
|
|
for _, line := range rendered {
|
|
lines = append(lines, line.text)
|
|
}
|
|
highlighted := strings.Join(lines, "\n")
|
|
if ansi.Strip(highlighted) != markdown {
|
|
t.Fatalf("highlighting changed text:\n%q\nwant:\n%q", ansi.Strip(highlighted), markdown)
|
|
}
|
|
if !strings.Contains(highlighted, "\x1b[") {
|
|
t.Fatalf("Markdown was not highlighted: %q", highlighted)
|
|
}
|
|
for index, line := range rendered {
|
|
if ansi.StringWidth(line.text) != ansi.StringWidth(ansi.Strip(line.text)) {
|
|
t.Fatalf("highlighted line %d changed width", index)
|
|
}
|
|
}
|
|
|
|
editor.Cursor = strings.Index(markdown, "code")
|
|
editor.handleKey(runeKey("s"), true)
|
|
editor.handleKey(runeKey("C"), true)
|
|
if editor.Text != strings.Replace(markdown, "code", "Code", 1) {
|
|
t.Fatalf("highlighted edit changed wrong rune: %q", editor.Text)
|
|
}
|
|
}
|
|
|
|
func TestMarkdownHighlightTokenKinds(t *testing.T) {
|
|
const markdown = "# Heading\nText **strong** and *emphasis* with `code` and [link](target)\n<!-- comment -->\n"
|
|
styles := editorMarkdownStyles(markdown)
|
|
assertStyleAt := func(fragment string, want editorMarkdownStyle) {
|
|
t.Helper()
|
|
index := len([]rune(markdown[:strings.Index(markdown, fragment)]))
|
|
if styles[index] != want {
|
|
t.Fatalf("style for %q = %d, want %d", fragment, styles[index], want)
|
|
}
|
|
}
|
|
assertStyleAt("# Heading", editorMarkdownHeading)
|
|
assertStyleAt("**strong**", editorMarkdownStrong)
|
|
assertStyleAt("*emphasis*", editorMarkdownEmphasis)
|
|
assertStyleAt("`code`", editorMarkdownCode)
|
|
assertStyleAt("link", editorMarkdownLink)
|
|
assertStyleAt("target", editorMarkdownDestination)
|
|
assertStyleAt("<!-- comment -->", editorMarkdownComment)
|
|
}
|
|
|
|
func joinEditorLines(lines []editorRenderedLine) string {
|
|
var values []string
|
|
for _, line := range lines {
|
|
values = append(values, line.text)
|
|
}
|
|
return strings.Join(values, "")
|
|
}
|
|
|
|
func runeKey(value string) tea.KeyMsg {
|
|
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(value)}
|
|
}
|