rosed

package module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2023 License: MIT Imports: 6 Imported by: 6

README

rosed

Tests Status Badge Go Reference

Pronounced "ROSE-edd". Fluent text editing and layout library for Go.

This library treats text as a sequence of "grapheme clusters" (user-visible characters) as opposed to actual runes or bytes. This allows wrapping and other character-counting/index operations to function in an expected way regardless of whether visible characters are made up of Unicode decomposed sequences or pre-composed sequences.

Graphemes? What Are Those?

Take something like "Ý", which appears to be one single character but behind the scenes could be a single precomposed character rune or multiple runes that compose into a single apparent character using Unicode combining marks, such as the Unicode codepoint for uppercase "Y" (U+0079) followed by the codepoint for the combining acute accent (U+0301) (not to be confused with the non-combining codepoint for the acute accent symbol by itself, U+00B4. Naturally. Isn't Unicode fun?).

Regardless of its representation in runes, we as humans see the Y with an acute accent and will usually say "ah, that is one character", and that is a grapheme cluster (or just grapheme for short when referring to the thing being displayed as opposed to the runes that make up that thing). This library takes that opinion as well, and will operate on text in a consistent way regardless of how each grapheme is represented.

You can find out more information on the Unicode definition of "grapheme cluster" under Chapter 2 of the Unicode Standard, in particular see the heading '"Characters" and Grapheme Clusters" at the end of section 2.11, "Combining Characters". If you want, you can also dig into section 2.12, "Equivalant Sequences", to see how different grapheme clusters (codepoint sequences) can translate into the same visual grapheme.

What This Library Is Not
  • Performant - Performance is slowly improved with releases but this library aims to be easy to use as its first goal.
  • Collating - This libarary does not support Unicode collation. It may come at a later date.

Example Usages

This library aims to make it easy to work with text in a CLI environment, or really any that has fixed-width text in it. This section shows some of the things you can do with rosed, but by no means is it all of it. Check the Full Reference Docs for a listing of all available functions as well as examples of use.

In general, to use rosed, first create an Editor, use it to modify text, then get it back by converting it to a string. As easy as plucking a rose from the bed of thorns that grows in the midst of the most eldritch of gardens. Perhaps easier, in fact.

Wrap Text

You can use rosed to wrap text to a certain width:

Try it on the Go Playground

text := "It's hard, being a kid and growing up. It's hard and nobody understands."

wrapped := rosed.Edit(text).Wrap(30).String()

fmt.Println(wrapped)

Output:

It's hard, being a kid and
growing up. It's hard and
nobody understands.

You can do this even if it's already been hard-wrapped:

Try it on the Go Playground

text := "It's hard, being\na kid and growing up.\nIt's hard and nobody\nunderstands."

wrapped := rosed.Edit(text).Wrap(30).String()

fmt.Println(wrapped)
Justify Text

You can use rosed to ensure that all lines in the text take up the same width, and add spacing equally between words on each line to ensure this is the case. This is also known as justifying a block of text:

Try it on the Go Playground

text := "I WARNED YOU ABOUT STAIRS\n"
text += "BRO! I TOLD YOU DOG!\n"
text += "I TOLD YOU MAN!"

justified := rosed.Edit(text).Justify(30).String()

fmt.Println(justified)

Output:

I   WARNED  YOU  ABOUT  STAIRS
BRO!    I   TOLD   YOU    DOG!
I TOLD YOU MAN!

You could combine this with wrapping to account for the lines that didn't quite end up the same length as the others:

Try it on the Go Playground

text := "Your name is KANAYA MARYAM. You are one of the few of your "
text += "kind who can withstand the BLISTERING ALTERNIAN SUN, and "
text += "perhaps the only who enjoys the feel of its rays. As such, "
text += "you are one of the few of your kind who has taken a shining "
text += "to LANDSCAPING. You have cultivated a lush oasis."

wrappedAndJustified := rosed.Edit(text).Wrap(50).Justify(50).String()

fmt.Println(wrappedAndJustified)

Output:

Your name is KANAYA MARYAM. You are one of the few
of  your kind  who  can  withstand  the BLISTERING
ALTERNIAN SUN, and perhaps the only who enjoys the
feel  of its rays. As such, you are one of the few
of   your  kind   who  has  taken  a  shining   to
LANDSCAPING. You have cultivated a lush oasis.
Definitions Table

You can use this library to build a table of terms and their definitions:

Try it on the Go Playground

aradiaDef := "Once had a fondness for ARCHEOLOGY, though now has trouble "
aradiaDef += "recalling this passion."

tavDef := "Known to be heavily arrested by FAIRY TALES AND FANTASY STORIES. "
tavDef += "Has an acute ability to COMMUNE WITH THE MANY CREATURES OF "
tavDef += "ALTERNIA."

solluxDef := "Is good at computers, and knows ALL THE CODES. All of them."

karkatDef := "Has a passion for RIDICULOUSLY TERRIBLE ROMANTIC MOVIES AND "
karkatDef += "ROMCOMS. Should really be EMBARRASSED for liking this DREADFUL "
karkatDef += "CINEMA, but for some reason is not."

defs := [][2]string{
	{"Aradia", aradiaDef},
	{"Tavros", tavDef},
	{"Sollux", solluxDef},
	{"Karkat", karkatDef},
}

const (
	position = 0
	width    = 80
)

defTable := rosed.Edit("").InsertDefinitionsTable(position, defs, width).String()

fmt.Println(defTable)

Output:

  Aradia  - Once had a fondness for ARCHEOLOGY, though now has trouble recalling
            this passion.

  Tavros  - Known to be heavily arrested by FAIRY TALES AND FANTASY STORIES. Has
            an acute ability to COMMUNE WITH THE MANY CREATURES OF ALTERNIA.

  Sollux  - Is good at computers, and knows ALL THE CODES. All of them.

  Karkat  - Has a passion for RIDICULOUSLY TERRIBLE ROMANTIC MOVIES AND ROMCOMS.
            Should really be EMBARRASSED for liking this DREADFUL CINEMA, but
            for some reason is not.
Two-Column Layout

You can use this library to create two columns of text:

Try it on the Go Playround

left := "You have a feeling it's going to be a long day."

right := "A familiar note is produced. It's the one Desolation plays to "
right += "keep its instrument in tune."

const (
	position    = 0
	middleSpace = 2
	width       = 60
	leftPercent = 0.33
)

columns := rosed.Edit("").
	InsertTwoColumns(position, left, right, middleSpace, width, leftPercent).
	String()

fmt.Println(columns)

Output:

You have a feeling   A familiar not is produced. It's the
it's going to be a   one Desolation plays to keep its
long day.            instrument in tune.
Custom Functionality

Do you like the idea of operating on lines with customizability but rosed's built-in Editor functions aren't enough for you? Use Apply to give your own function to run on each line, or ApplyParagraphs to run it on each paragraph.

For instance, to upper-case every line while giving each a line number:

Try it on the Go Playground

text := "She went too far this time and she knows it.\n"
text += "She's got to pay.\n"
text += "Justice is long overdue.\n"

lineNumberMaker := func(idx int, line string) []string {
	upperLine := strings.ToUpper(line)
	line = fmt.Sprintf("LINE %d: %s", idx+1, upperLine)
	return []string{
		line,
	}
}

customized := rosed.Edit(text).Apply(lineNumberMaker).String()

fmt.Println(customized)

Output:

LINE 1: SHE WENT TOO FAR THIS TIME AND SHE KNOWS IT.
LINE 2: SHE'S GOT TO PAY.
LINE 3: JUSTICE IS LONG OVERDUE.
Sub-Section Selection

You can open a sub-editor on a sub-section of text in an Editor to only affect that part of the text.

What if we forgot to tab some lines of an indented paragraph that comes after a non-indented paragraph? Oh no! We better fix that, quick:

Try it on the Go Playground

text := "Your name is NEPETA LEIJON.\n"
text += "\n"
text += "\tYou live in a CAVE that is also a HIVE.\n"
text += "But still mostly just a cave.\n"                 // this line should have an indent in it
text += "You like to engage in FRIENDLY ROLE PLAYING.\n"  // so should this one
text += "\tBut not the DANGEROUS KIND.\n"
text += "\tIt's TOO DANGEROUS!"

corrected := rosed.Edit(text).Lines(3, 5).Indent(1).String()

fmt.Println(corrected)

Output:

Your name is NEPETA LEIJON.

	You live in a CAVE that is also a HIVE.
	But still mostly just a cave.
	You like to engage in FRIENDLY ROLE PLAYING.
	But not the DANGEROUS KIND.
	It's TOO DANGEROUS!

Contributing

This library uses its GitHub Issues Page for coordination of work. If you'd like to assist with an open issue, feel free to drop a comment on the issue mentioning your interest. Then, fork the repo into your own copy, make the changes, then raise a PR to bring them in.

If there's a bug you'd like to report or new feature you'd like to see in this library, feel free to open a new issue for it.

Example Text Source

This explains the refrance [sic] of the sample text, most of the samples, test cases, and example texts used in this library's documentation have been sampled from the text of Homestuck, a primarily text-based web comic with occasional animations whose topic matter ranges from data-structure jokes to an analysis of the self and how our past makes us. Sadly due to Flash dying a lot of the site is non-functional.

May it live on in our hearts and naming conventions longer than the darkness of the furthest ring. Or, you know, just generally exist in The Unofficial Homestuck Collection.

Documentation

Overview

Package rosed is a library for manipulating and laying out fixed-width text. It assumes that all text is encoded in UTF-8, the default encoding of source code string literals in Go.

"rosed" is pronounced as "Rose-Edd" but any pronunciation that can be understood to mean this library is accepted.

Grapheme-Awareness

Functions in rosed that operate on text in a way that indexes characters or otherwise needs to count them (for instance, for wrapping purposes) do so by their grapheme clusters, not their Unicode codepoints, runes, or bytes. A grapheme is a single graphical unit that a human viewer would call a single character, but may in fact consist of several Unicode codepoints and their constituent runes and bytes; one or more codepoints that make up a single grapheme is known as a grapheme cluster.

For example, the sequence "é" is a single grapheme, as is "e". But in the case of "é", it may be represented in Unicode either by the single codepoint U+00E9 ("Latin Small Letter E With Acute"), or by the codepoint sequence U+0065, U+0301 ("Latin Small Letter E" followed by "Combining Acute Accent"). In Go source code, the later would be represented in UTF-8 strings as two runes (and whatever bytes it would take to make up those runes), meaning that both iteration over the string and unicode/utf8.RuneCountInString would return a higher number of runes than perhaps would be expected.

// This word appears to be 7 characters long:
precomposed := "fiancée"
decomposed := "fiance\u0301e"

// and in fact, if printed, both show the same sequence to a human
// user:
fmt.Println(precomposed)  // shows "fiancée"
fmt.Println(decomposed)   // ALSO shows "fiancée"

fmt.Println(utf8.RuneCountInString(precomposed))  // prints 7
fmt.Println(utf8.RuneCountInString(decomposed))   // prints 8 (?!)

Try it on the Go Playground: https://go.dev/play/p/UiyXIHhWn_0

See UAX #15: Unicode Normalization Forms for more info on the forms that a Unicode string can take based on how it represents graphemes.

This library implements the algorithms described in UAX #29: Unicode Text Segmentation to recognize where in text the boundaries of each grapheme cluster are and correctly index by grapheme. This means it transparently handles all ways that a single "human-readable" character could be represented.

wrapped1 := rosed.Edit("My fiancée and I went to the bistro").Wrap(10).String()
wrapped2 := rosed.Edit("My fiance\u0301e and I went to the bistro").Wrap(10).String()

// because rosed is grapheme aware, both representations are wrapped the
// same way

fmt.Println(wrapped1)
fmt.Println("")
fmt.Println(wrapped2)

// Both of the above print out:
//
// My fiancée
// and I went
// to the
// bistro.

Try it on the Go Playground: https://go.dev/play/p/3CNStlPoWmE

Note that this library does not handle Unicode-normalized collation; that may be covered at a later time but for now it was deemed too much to implement for version 1.0, given the large amount of data from Unicode that must be added to the library for it to function and possible dependence on locale-specific settings.

Basic Usage

All editing starts with an Editor. The zero-value can be used to start with blank text, otherwise Edit can be called to give the text to work with. Additionally, if desired, the Editor can have its Text property set to the text to operate on.

ed1 := rosed.Editor{}
ed2 := rosed.Edit("my text")
ed3 := rosed.Editor{Text: "my text"}

From that point, Editor functions can be called to modify the text in it. Editors are considered to be immutable by all functions that operate on them; these functions will return a new Editor with its Text set to the result of an operation as opposed to actually modifying the Text of the Editor they are called on.

// Here's an example of using Overtype to replace some text:
ed := rosed.Edit("How are you, Miss Lalonde?")
overtypedEd := ed.Overtype(4, "goes it")

// The original Editor is unchanged:
originalText := ed.String()
fmt.Printf("originalText: %q\n", originalText)
// Output:
// originalText: "How are you, Miss Lalonde?"

// But the text in the editor returned by Overtype is modified:
output1 := overtypedEd.String()
fmt.Printf("output1     : %q\n", output1)
// Output:
// output1     : "How goes it, Miss Lalonde?"

// Alternatively, all calls can be chained:
output2 := rosed.Edit("How are you, Miss Lalonde?").Overtype(4, "goes it").String()
fmt.Printf("output2     : %q\n", output2)
// Output:
// output2     : "How goes it, Miss Lalonde?"

Try it on the Go Playground: https://go.dev/play/p/XjP0123dDtQ

Editor.Overtype is not a particularly exciting example of this library's use; Editor.Wrap, Editor.InsertTwoColumns, and Editor.InsertDefinitionsTable are some examples of the more complex operations that can be performed on an Editor.

Options For Behavior Control

Some aspects of this library are controlled using consistent options set on an Editor. These are stored in an Options struct, and can be applied to an Editor either by assignment directly to Editor.Options or by calling Editor.WithOptions to get an Editor with those options set on it.

The Options struct itself supports a fluent interface for changing its values, using its WithX methods.

// can create Options struct with members set directly:
opts1 := rosed.Options{PreserveParagraphs: true}

// or by taking an existing Options and calling WithX functions on it:
opts2 := rosed.Options{}.WithPreserveParagraphs(true)

Editor functions will mention which options they are affected by and how they are affected by them in the documentation comment for the function.

Note that Editor.WithOptions, like all other Editor functions, treats the Editor it operates on as immutable. It returns an Editor that has those options set on it, but the Editor it is called on is itself unchanged. To permanently set the Options on a particular Editor, you can assign an Options struct to Editor.Options manually.

// this is perfectly acceptable:
ed := rosed.Editor{
    Options: rosed.Options{
        LineSeparator: "\r\n",
    },
}

If you want to avoid setting the options on the Editor and thus affecting all further operations, you can give the options for a particular call of an Editor function by calling the Opts version of it. If there is no Opts version, then that function is not affected by any options.

input := "More then ever, you feel, what's the word you're looking for? Of course. Housetrapped."
opts := rosed.Options{PreserveParagraphs: true}
ed := rosed.Edit(input)

// Options can be set by giving them to the Editor prior to calling a function:
output := ed.WithOptions(opts).Wrap(10).String()

// ...or by calling the Opts version of the function:
output := ed.WrapOpts(10, opts).String()

Editing Partial Sections

To edit only a portion of the text in an Editor, a sub-editor can be created using Editor.Chars, Editor.CharsFrom, Editor.CharsTo, Editor.Lines, Editor.LinesFrom, or Editor.LinesTo. The Editor retured from these functions will perform operations only on the section specified, and any positions or lengths used in it will be relative to that sub-section's start and end. Writing past the end of the sub-editor's text is allowed and does not affect the text outside of the subsection.

These changes can be rolled up to the parent text by calling Editor.Commit. This will produce an Editor that consists of the full text prior to selecting a particular section, with all changes made to the sub-section merged in.

ed := rosed.Edit("Hello, World!")

// edits only the ", " part
subEd := ed.Chars(5, 7)

// Make subEd.Text be "\t, "
subEd = subEd.Indent(1)

// And bring it all back together
ed = subEd.Commit()

// Get the result string, which will be "Hello\t, world!"
output1 := ed.String()

// The same steps using the fluent style:
output2 := rosed.Edit("Hello, World!").Chars(5, 7).Indent(1).Commit().String()

If multiple sub-editors have been made, Editor.CommitAll can be used to apply all changes recursively and return an Editor operating on the full text. Calling Editor.String on a sub-editor will also cause CommitAll to be called, making it an excellent choice to get Editor.Text without needing to be concerned with whether it is a sub-editor.

// Commit() or CommitAll() is not needed before string:
output := rosed.Edit("Hello, World!").Chars(5, 7).Indent(1).String()

Note that it is possible to create a sub-editor, and then create another sub-editor off of the same original Editor. This may have unexpected results and is not the intended use of sub-editors.

Negative Indexing

Some Editor functions accept one or more indexes, either of characters (graphemes specifically, in the context of this library), lines, or other textual elements. The rosed library has implemented the use of negative indexes to make it a easier to reference positions releative to the end of the sequence they are in.

What this means is that a function such as Editor.Insert can receive a negative index for its position argument, and it will interpret it as relative to the end of the text. For instance, -1 would be the last character, -2 would be the second-to-last character, etc. If a negative index would specify someting before the start of the collection, it is automatically assumed to be 0.

ed := rosed.Edit("John, Dave, and Jade")

// whoops, we forget to add someone 9 characters before the end!
ed = ed.Insert(-9, "Rose, ")

// this will be the correct "John, Dave, Rose, and Jade"
output := ed.String()

Many functions that do this will mention it explicitly, but all functions that accept positions can interpret negative positions.

Custom Operations

The rosed package supports several operations on Editor text out of the box, however these are insufficient for all uses. To address cases where custom functionality may be needed, two functions are provided. They are Editor.Apply and Editor.ApplyParagraphs. These allow the user to provide a custom function to operate on text on a per-line or per-paragraph basis.

textBody := "John\nRose\nDave\nJade"

namerFunc := func(lineIdx int, line string) []string {
    newStr := fmt.Sprintf("Beta Kid #%d: %s", lineIdx + 1, line)
    return []string{newStr}
}

output := rosed.Edit(textBody).Apply(namerFunc).String()
// The output will be:
//
// Beta Kid #1: John
// Beta Kid #2: Rose
// Beta Kid #3: Dave
// Beta Kid #4: Jade

Do note that as the LineOperation or ParagraphOperation passed to the above functions will be user-defined, they will not be grapheme-aware unless the user ensures that this is the case.

Example (End)

This example uses the rosed.End constant to specify the end of the text as the editor is modified.

fullText := Edit("Hello!\n").
	Insert(End, "This is a line\n").
	Insert(End, "And this is another").
	String()

fmt.Println(fullText)
Output:

Hello!
This is a line
And this is another

Index

Examples

Constants

View Source
const (
	// DefaultIndentString is the default string used for a single horizontal
	// indent.
	DefaultIndentString = "\t"

	// DefaultLineSeparator is the default line separator sequence.
	DefaultLineSeparator = "\n"

	// DefaultParagraphSeparator is the default sequence that separates
	// paragraphs.
	DefaultParagraphSeparator = "\n\n"

	// DefaultTableCharSet is the default characters used to draw table borders.
	DefaultTableCharSet = "+|-"
)
View Source
const End = -int(^uint(0)>>1) - 1

End is a constant that if passed to a position argument, represents a contextual "end of text" as of the calling of the operation it is passed to. Will always refer to the end of text.

Variables

This section is empty.

Functions

This section is empty.

Types

type Alignment added in v1.1.0

type Alignment int

Alignment is the type of alignment to apply to text. It is used in the Editor.Align function.

const (
	// None is no alignment and is the zero value of an Alignment.
	None Alignment = iota

	// Left is alignment to the left side of the text.
	Left

	// Right is alignment to the right side of the text.
	Right

	// Center is alignment to the center of the text.
	Center
)

type Editor

type Editor struct {
	// Text is the string that will be operated on.
	Text string

	// The Options that the Editor will use for the next operation. These can
	// be modified prior to the operation.
	Options Options
	// contains filtered or unexported fields
}

Editor performs transformations on text. It is the primary way to edit text using the rosed package.

The zero value is an Editor ready to operate on the empty string. Alternatively, Edit can be called to produce an Editor ready to operate on the passed-in string. There is no functional difference between a zero-value Editor having its Text property set manually and calling Edit with a string; they will produce identical Editors.

All operations on an Editor treat it as immutable; calling an operation returns a new Editor with its Text property set to the result of the operation. It is valid to manually set properties of an Editor and this will not break the assumptions made by operations called on it.

Editor has an Options member for including an Options instance to control the behavior of certain functions called on it. If left unset, functions called on the Editor will treat it as though it has been set with the equivalent of calling Options.WithDefaults on an empty Options struct.

Sub-Editor Functions

Some Editor functions produce a sub-editor, whose Text field will contain only the sub-section of text specified. Editing the parent's Text field after the sub-editor has been created will have no effect on the sub-editor or any Editor produced from it, but note that it may have an affect on the result of calling Commit() on the sub-editor and as a result is not the inteded usage of sub-editors.

The sub-editor can be merged back into the original Editor by calling Editor.Commit on the sub-editor. Alternatively, all such sub-editors can be merged recursively up to the root Editor by calling Editor.CommitAll on the sub-editor.

func Edit

func Edit(text string) Editor

Edit creates an Editor with its Text property set to the given string and with Options set to their default values.

Example
ed := Edit("sample text")

fmt.Println(ed.Text)
Output:

sample text

func (Editor) Align added in v1.1.0

func (ed Editor) Align(align Alignment, width int) Editor

Align makes each line follow the given alignment. If None is given for the alignment, this operation has no effect. If a line is not the given width, spaces are added to the unaligned-to end until the line is that width. If a line (minus any leading/trailing space being removed by the alignment) is already more than the given width, it will not be affected.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This function is affected by the following Options:

  • LineSeparator is used to separate lines of input.
  • ParagraphSeparator is the separator used to split paragraphs. It will only have effect if PreserveParagraphs is set to true.
  • PreserveParagraphs gives whether to respect paragraphs instead of considering them text to be aligned. If set to true, the text is split into paragraphs by ParagraphSeparator, then the align is applied to each paragraph.
  • NoTrailingLineSeparators specifies whether the function should consider a final instance of LineSeparator to be ending the prior line or giving the start of a new line. If NoTrailingLineSeparators is true, a trailing LineSeparator is considered to start a new (empty) line; additionally, the align will be called at least once for an empty string. If NoTrailingLineSeparators is set to false and the Editor text is set to an empty string, the align will not be called even once.
Example (Center)

This example shows use of Align to center each line of text.

text := "Your name is TEREZI PYROPE.\n"
text += "\n"
text += "You are pretty enthusiastic about dragons.\n"
text += "But you have a PARTICULAR AFFECTION\n"
text += "for their COLORFUL SCALES, which you gather\n"
text += "and use to decorate your hive.\n"

ed := Edit(text)

ed = ed.Align(Center, 45)

// also add a pipe char at start and end of each line to show that space is
// added to fill out each line to the specified width:
ed = ed.Apply(func(idx int, line string) []string {
	return []string{"|" + line + "|"}
})

fmt.Println(ed.String())
Output:

|         Your name is TEREZI PYROPE.         |
|                                             |
|  You are pretty enthusiastic about dragons. |
|     But you have a PARTICULAR AFFECTION     |
| for their COLORFUL SCALES, which you gather |
|        and use to decorate your hive.       |
Example (Left)

This example shows use of Align to take inconsistently tabbed input and normalize each line to no indent before doing further operations.

text := "\t\t\tYour name is TEREZI PYROPE.\n"
text += "\n"
text += "\t  You are pretty enthusiastic about dragons.\n"
text += "     But you have a PARTICULAR AFFECTION\n"
text += "\t for their COLORFUL SCALES, which you gather\n"
text += "\t\tand use to decorate your hive.\n"

ed := Edit(text)

ed = ed.Align(Left, 45)

// also add a pipe char at start and end of each line to show that space is
// added to fill out each line to the specified width:
ed = ed.Apply(func(idx int, line string) []string {
	return []string{"|" + line + "|"}
})

fmt.Println(ed.String())
Output:

|Your name is TEREZI PYROPE.                  |
|                                             |
|You are pretty enthusiastic about dragons.   |
|But you have a PARTICULAR AFFECTION          |
|for their COLORFUL SCALES, which you gather  |
|and use to decorate your hive.               |
Example (Right)

This example shows use of Align to make every line align to the right side.

text := "Your name is TEREZI PYROPE.\n"
text += "\n"
text += "You are pretty enthusiastic about dragons.\n"
text += "But you have a PARTICULAR AFFECTION\n"
text += "for their COLORFUL SCALES, which you gather\n"
text += "and use to decorate your hive.\n"

ed := Edit(text)

ed = ed.Align(Right, 45)

// also add a pipe char at start and end of each line to show that space is
// added to fill out each line to the specified width:
ed = ed.Apply(func(idx int, line string) []string {
	return []string{"|" + line + "|"}
})

fmt.Println(ed.String())
Output:

|                  Your name is TEREZI PYROPE.|
|                                             |
|   You are pretty enthusiastic about dragons.|
|          But you have a PARTICULAR AFFECTION|
|  for their COLORFUL SCALES, which you gather|
|               and use to decorate your hive.|

func (Editor) AlignOpts added in v1.1.0

func (ed Editor) AlignOpts(align Alignment, width int, opts Options) Editor

AlignOpts makes each line follow the given alignment using the provided options.

This is identical to Editor.Align but provides the ability to set Options for the invocation.

Example

This example shows use of a custom line separator with Align to respect text that uses the HTML tag "<br/>\n" to separate lines.

text := "Your name is TEREZI PYROPE.<br/>\n"
text += "<br/>\n"
text += "You are pretty enthusiastic about dragons.<br/>\n"
text += "But you have a PARTICULAR AFFECTION<br/>\n"
text += "for their COLORFUL SCALES, which you gather<br/>\n"
text += "and use to decorate your hive.<br/>\n"

opts := Options{
	LineSeparator: "<br/>\n",
}

ed := Edit(text)

ed = ed.AlignOpts(Center, 45, opts)

// also add a pipe char at start and end of each line to show that space is
// added to fill out each line to the specified width:
/*ed = ed.Apply(func(idx int, line string) []string {
	return []string{"|" + line + "|"}
})*/

fmt.Println(ed.String())
Output:

         Your name is TEREZI PYROPE.         <br/>
                                             <br/>
  You are pretty enthusiastic about dragons. <br/>
     But you have a PARTICULAR AFFECTION     <br/>
 for their COLORFUL SCALES, which you gather <br/>
        and use to decorate your hive.       <br/>

func (Editor) Apply

func (ed Editor) Apply(op LineOperation) Editor

Apply applies the given LineOperation to each line in the text. Line termination at the last line is transparently handled as per the options set on the Editor.

The LineOperation should assume it will receive each line without its line terminator, and must assume that anything it returns will have re-adding the separator to it handled by the caller.

This function is affected by the following Options:

  • LineSeparator specifies what string in the source text should be used to delimit lines to be passed to the LineOperation.
  • NoTrailingLineSeparators specifies whether the function should consider a final instance of LineSeparator to be ending the prior line or giving the start of a new line. If NoTrailingLineSeparators is true, a trailing LineSeparator is considered to start a new (empty) line; additionally, the LineOperation will be called at least once for an empty string. If NoTrailingLineSeparators is set to false and the Editor text is set to an empty string, the LineOperation will not be called.
Example

This example shows the use of Apply to add a numbered prefix to every line.

namerFunc := func(lineIdx int, line string) []string {
	newStr := fmt.Sprintf("Alpha Kid #%d: %s", lineIdx+1, line)
	return []string{newStr}
}

ed := Edit("Jane\nJake\nDirk\nRoxy").Apply(namerFunc)

fmt.Println(ed.String())
Output:

Alpha Kid #1: Jane
Alpha Kid #2: Jake
Alpha Kid #3: Dirk
Alpha Kid #4: Roxy

func (Editor) ApplyOpts

func (ed Editor) ApplyOpts(op LineOperation, opts Options) Editor

ApplyOpts applies the given LineOperation to each line in the text, using the provided options.

This is identical to Editor.Apply but provides the ability to set Options for the invocation.

Example

This example uses options to tell the Editor to use a custom LineSeparator of the HTML tag "<br/>", and it tells it that any trailing line ending is in fact the start of a new, empty line, which should be processed by the function.

alphaKids := 0
namerFunc := func(lineIdx int, line string) []string {
	if line == "" {
		return []string{"Nobody here!"}
	}

	alphaKids++
	newStr := fmt.Sprintf("Alpha Kid #%d: %s", alphaKids, line)
	return []string{newStr}
}

opts := Options{
	NoTrailingLineSeparators: true,
	LineSeparator:            "<br/>",
}

// have a trailing line separator so we get the extra call of our func
// due to NoTrailingLineSeparators
ed := Edit("Jane<br/><br/>Dirk<br/>Roxy<br/>").ApplyOpts(namerFunc, opts)

fmt.Println(ed.String())
Output:

Alpha Kid #1: Jane<br/>Nobody here!<br/>Alpha Kid #2: Dirk<br/>Alpha Kid #3: Roxy<br/>Nobody here!

func (Editor) ApplyParagraphs

func (ed Editor) ApplyParagraphs(op ParagraphOperation) Editor

ApplyParagraphs applies the given ParagraphOperation to each paragraph in the text of the Editor.

The ParagraphOperation should assume it will receive each paragraph without its paragraph separator, and must assume that anything it returns will have re-adding the separator to it handled by the caller.

When the ParagraphSeparator of the Editor's options is set to a sequence that includes non-whitespace characters that take up horizontal space, the ParagraphOperation will receive the prefix and suffix of the paragraph that would be in the joined string due to the separator, with variables sepPrefix and sepSuffix. This is not intended to allow the operation to add them back in manually, as that is handled by the caller, but for it to perform book-keeping and length checks and act accordingly, such when attempting to output something that is intended to be aligned.

Unlike with LineSeparator, a ParagraphSeparator is always considered a separator, not a terminator, so the affixes may vary per paragraph if the ParagraphSeparator has line breaks in it; in that case the first paragraph will have an empty prefix, the last paragraph will have an empty suffix, and all other paragraphs will have non-empty prefixes and suffixes.

For an example of a ParagraphOperator that uses sepPrefix and sepSuffix and a custom ParagraphSeparator that makes them non-empty, see the example for Editor.ApplyParagraphsOpts.

Note that treating the paragraph separator as a splitter and not a terminator means that the ParagraphOperation is always called at least once, even for an empty editor.

This function is affected by the following Options:

  • ParagraphSeparator specifies the string that paragraphs are split by.
Example

This example names the people in each paragraph in the input text based on who is present. Since no options are specified, it uses the default ParagraphSeparator of "\n\n".

text := ""
text += "John\n" // paragraph 1
text += "Rose\n"
text += "Dave\n"
text += "Jade\n"
text += "\n"
text += "Jane\n" // paragraph 2
text += "Jake\n"
text += "Dirk\n"
text += "Roxy\n"
text += "\n"
text += "Aradia\n" // paragraph 3
text += "Tavros\n"
text += "Sollux\n"
text += "Karkat"

paraOp := func(idx int, para, sepPrefix, sepSuffix string) []string {
	var newPara string
	if strings.Contains(para, "John") {
		newPara = "Beta Kids:\n" + para
	} else if strings.Contains(para, "Jane") {
		newPara = "Alpha Kids:\n" + para
	} else {
		newPara = "Someone Else:\n" + para
	}
	return []string{newPara}
}

ed := Edit(text).ApplyParagraphs(paraOp)

fmt.Println(ed.String())
Output:

Beta Kids:
John
Rose
Dave
Jade

Alpha Kids:
Jane
Jake
Dirk
Roxy

Someone Else:
Aradia
Tavros
Sollux
Karkat

func (Editor) ApplyParagraphsOpts

func (ed Editor) ApplyParagraphsOpts(op ParagraphOperation, opts Options) Editor

ApplyParagraphsOpts applies the given ParagraphOperation to each paragraph in the text of the Editor, using the provided options.

This is identical to Editor.ApplyParagraphs but provides the ability to set Options for the invocation.

Example

This example uses options to tell the Editor to use a custom ParagraphSeparator for splitting paragrahs, and also shows how sepPrefix and sepSuffix are set.

opts := Options{ParagraphSeparator: "<P1>\n<P2>"}
ed := Edit("para1<P1>\n<P2>para2<P1>\n<P2>para3<P1>\n<P2>para4")

paraOp := func(idx int, para, sepPrefix, sepSuffix string) []string {
	newPara := fmt.Sprintf("(PREFIX=%s,PARA=%s,SUFFIX=%s)", sepPrefix, para, sepSuffix)
	return []string{newPara}
}

ed = ed.ApplyParagraphsOpts(paraOp, opts)

fmt.Println(ed.String())
Output:

(PREFIX=,PARA=para1,SUFFIX=<P1>)<P1>
<P2>(PREFIX=<P2>,PARA=para2,SUFFIX=<P1>)<P1>
<P2>(PREFIX=<P2>,PARA=para3,SUFFIX=<P1>)<P1>
<P2>(PREFIX=<P2>,PARA=para4,SUFFIX=)

func (Editor) CharCount added in v1.1.0

func (ed Editor) CharCount() int

CharCount returns the number of grapheme clusters in the Editor's text. This is the number of characters a human viewer would count if the text were displayed or printed.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

Example

This example gets the length of several different strings.

emptyCount := Edit("").CharCount()
fmt.Println(emptyCount)

testCount := Edit("test").CharCount()
fmt.Println(testCount)

//lint:ignore ST1018 because go-staticcheck dislikes emoji literals with ZWJ sequences
emojiCount := Edit("👩‍❤️‍💋‍👩").CharCount()
fmt.Println(emojiCount)
Output:

0
4
1

func (Editor) Chars

func (ed Editor) Chars(start, end int) Editor

Chars produces an Editor to operate on a subset of the characters in the Editor's text. The returned Editor operates on text from the nth character up to (but not including) the ith character, where n is start and i is end.

The start or end parameter may be negative, in which case it will be relative to the end of the string; -1 would be the index of the last character, -2 would be the index of the second-to-last character, etc.

If one of the parameters specifies an index that is past the end of the string, that index is assumed to be the end of the string. If either specify an index that is before the start of the string, it is assumed to be 0.

If end is less than start, it is assumed to be equal to start.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This is a Sub-Editor function. See the note on Editor for more info.

Example

This example gets a sub-Editor for the the "ell" part of "Hello!".

ed := Edit("Hello!")
subEd := ed.Chars(1, 4)

// Not doing Editor.String for the example because that would call Commit
// and get back the starting string.
fmt.Println(subEd.Text)
Output:

ell

func (Editor) CharsFrom

func (ed Editor) CharsFrom(start int) Editor

CharsFrom produces an Editor to operate on a subset of the characters in the Editor's text. The returned Editor operates on text from the nth character up to the end of the text, where n is start.

Calling this function is identical to calling Editor.Chars with the given start and with end set to the end of the text.

Example

This example gets a sub-Editor for the the "ello!" part of "Hello!".

ed := Edit("Hello!")
subEd := ed.CharsFrom(1)

// Not doing Editor.String for the example because that would call Commit
// and get back the starting string.
fmt.Println(subEd.Text)
Output:

ello!

func (Editor) CharsTo

func (ed Editor) CharsTo(end int) Editor

CharsTo produces an Editor to operate on a subset of the characters in the Editor's text. The returned Editor operates on text from the first character up to but not including the nth character, where n is end.

Calling this function is identical to calling Editor.Chars with the given end and with start set to the start of the text.

Example

This example gets a sub-Editor for the the "He" part of "Hello!".

ed := Edit("Hello!")
subEd := ed.CharsTo(2)

// Not doing Editor.String for the example because that would call Commit
// and get back the starting string.
fmt.Println(subEd.Text)
Output:

He

func (Editor) CollapseSpace

func (ed Editor) CollapseSpace() Editor

CollapseSpace converts all consecutive whitespace characters to a single space character.

This function is affected by the following Options:

  • LineSeparator is always considered whitespace, and will be collapsed into a space regardless of the classification of the characters within it.
Example

This example shows how CollapseSpace collapses all whitespace sequences into a single space, even those that have characters not typically encountered. As long as Unicode considers it a whitespace character, it will be collapsed.

text := "Some \n\n\n sample text \t\n\t  \t with  \u2002   \v\v whitespace"

ed := Edit(text).CollapseSpace()

fmt.Println(ed.String())
Output:

Some sample text with whitespace

func (Editor) CollapseSpaceOpts

func (ed Editor) CollapseSpaceOpts(opts Options) Editor

CollapseSpaceOpts converts all consecutive whitespace characters to a single space character using the provided options.

This is identical to Editor.CollapseSpace but provides the ability to set Options for the invocation.

Example

This example shows the use of Options with CollapseSpace to specify a LineSeparator that contains no whitespace. It shows that even LineSeparators without any Unicode space characters will be collapsed.

text := "Some \n\n\n sample<P>text \u202f <P> \t\n\t  \t with  \u2002   <P> whitespace"

opts := Options{
	LineSeparator: "<P>",
}

ed := Edit(text).CollapseSpaceOpts(opts)

fmt.Println(ed.String())
Output:

Some sample text with whitespace

func (Editor) Commit

func (ed Editor) Commit() Editor

Commit takes the substring that a sub-editor is operating on and merges it with its parent. It returns an Editor which is a copy of the current one but with its text set to the merged string.

If the Editor is already a full-text Editor, the merge operation simply copies the current text since there is nothing to merge with, so calling Commit returns an identical copy of the Editor.

Example (ManualReplacement)

This example shows how Commit can be used to commit the results of manually assigning a subeditor's Text field.

// get a subeditor on the first 7 chars, "Initial"
subEd := Edit("Initial string").Chars(0, 7)

// any Editor operation may be done, in our case we will manually update
// the Text of the subeditor to replace the word entirely
subEd.Text = "Test"

// now call Commit to send it back up and get our original editor
mergedEd := subEd.Commit()

fmt.Println(mergedEd.String())
Output:

Test string
Example (Operation)

This example shows how Commit can be used to commit the results of a normal Editor operation called on a subeditor.

// get a subeditor on the last 6 chars, "string"
subEd := Edit("Test string").CharsFrom(-6)

// any Editor operation may be done, in our case we will call the operation
// Indent to give it some leading whitespace
subEd = subEd.Indent(2)

// now call Commit to get the results merged in
mergedEd := subEd.Commit()

fmt.Println(mergedEd.String())
Output:

Test 		string

func (Editor) CommitAll

func (ed Editor) CommitAll() Editor

CommitAll takes the substring that a sub-editor is operating on and merges it with its parent recursively. Returns an Editor which is a copy of the current one but with its text set to the result of merging every sub-editor from the one CommitAll is called on up to the root Editor.

If the Editor is already a full-text Editor, the merge operation simply copies the current text since there is nothing to merge with, so calling CommitAll returns an identical copy of the Editor.

Example

This example shows the use of CommitAll to commit all outstanding subeditors. It edits a simple essay outline.

text := ""
text += "\t\tLine 1: An intro\n"
text += "Line 2: A body\n"
text += "\t\tLine 3: A conclusion"

startingEd := Edit(text)

// get a sub-editor for the second line, and fix the missing indent
firstLineSubEd := startingEd.Lines(1, 2)
firstLineSubEd = firstLineSubEd.Indent(2)

// 'body' is a boring word, let's get a subeditor on that part and do
// something about that.

// -5 to -1 because that is the last 4 chars other than the trailing newline
bodySubSubEd := firstLineSubEd.Chars(-5, -1)
bodySubSubEd = bodySubSubEd.Overtype(0, "rationale")

// changes are done, so commit all to get all changes merged
mergedEd := bodySubSubEd.CommitAll()

fmt.Println(mergedEd.String())
Output:

		Line 1: An intro
		Line 2: A rationale
		Line 3: A conclusion

func (Editor) Delete added in v0.2.0

func (ed Editor) Delete(start, end int) Editor

Delete removes text from the Editor. All text after the deleted sequence is moved left to the starting position of the deleted sequence.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

Example

This example shows the deletion of unwanted text in the editor.

ed := Edit("Here is some EXTRA text")

ed = ed.Delete(13, 19)

fmt.Println(ed.String())
Output:

Here is some text

func (Editor) Indent

func (ed Editor) Indent(level int) Editor

Indent adds an indent string at the start of each line in the Editor. The string used for a single level of indent is determined by Editor options and will be applied level times. If level is 0 or less, the text will be unchanged.

With default options set, this operation has no effect on an empty editor.

This function is affected by the following Options:

  • IndentStr is the sequence to use to indent a single level.
  • LineSeparator is the string that delimits lines.
  • NoTrailingLineSeparators alters whether LineSeparator is expected to be at the end of a complete line. If this is set to true, then a LineSeparator does not need to be present at the end of a complete line. Any trailing line separator for a non-empty editor is then considered to split the last line from a new, empty line, which will be indented. In addition, the empty editor will be considered to have a single line, which will be indented.
  • ParagraphSeparator is the separator used to split paragraphs. It will only have effect if PreserveParagraphs is set to true.
  • PreserveParagraphs gives whether to respect paragraphs instead of treating paragraph breaks as normal text. If set to true, the text is first split into paragraphs by ParagraphSeparator, then the indent is applied to each paragraph.
Example

This example shows a typical indent being applied to a list of people.

text := ""
text += "* Aradia\n"
text += "* Tavros\n"
text += "* Sollux\n"
text += "* Karkat\n"

ed := Edit(text)

ed = ed.Indent(1)

fmt.Println(ed.String())
Output:

	* Aradia
	* Tavros
	* Sollux
	* Karkat

func (Editor) IndentOpts

func (ed Editor) IndentOpts(level int, opts Options) Editor

IndentOpts adds an indent string at the start of each line in the Editor using the provided options.

This is identical to Editor.Indent but provides the ability to set Options for the invocation.

Example (IndentStr)

This example shows using options to specify a custom string to use for the indent.

text := ""
text += "* Nepeta\n"
text += "* Kanaya\n"
text += "* Terezi\n"
text += "* Vriska\n"

opts := Options{
	IndentStr: "==>",
}

ed := Edit(text)

ed = ed.IndentOpts(2, opts)

fmt.Println(ed.String())
Output:

==>==>* Nepeta
==>==>* Kanaya
==>==>* Terezi
==>==>* Vriska
Example (PreserveParagraphs)

This example shows using options to respect paragraph breaks.

text := ""
text += "Beta Kids:\n"
text += "* John\n"
text += "* Rose\n"
text += "* Dave\n"
text += "* Jade\n"
text += "\n"
text += "Alpha Kids:\n"
text += "* Jane\n"
text += "* Jake\n"
text += "* Dirk\n"
text += "* Roxy\n"

opts := Options{
	PreserveParagraphs: true,
}

ed := Edit(text)

ed = ed.IndentOpts(1, opts)

fmt.Println(ed.String())
Output:

 Beta Kids:
	* John
	* Rose
	* Dave
	* Jade

	Alpha Kids:
	* Jane
	* Jake
	* Dirk
	* Roxy

func (Editor) Insert

func (ed Editor) Insert(charPos int, text string) Editor

Insert adds a string to the text at the given position. The position is zero-indexed and refers to the visible characters in the text. At whatever position is given, the existing text is moved forward to make room for the new text.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

Example

This example inserts the text "burb" in the middle of the editor text.

ed := Edit("S world!")

ed = ed.Insert(1, "burb")

fmt.Println(ed.String())
Output:

Sburb world!

func (Editor) InsertDefinitionsTable

func (ed Editor) InsertDefinitionsTable(pos int, definitions [][2]string, width int) Editor

InsertDefinitionsTable creates a table of term definitions and inserts it into the text of the Editor. A definitions table is a two-column table that puts the terms being defined on the left and their definitions on the right. The terms are indented by two space characters.

A sample definitions table:

  John  - Has a passion for REALLY TERRIBLE MOVIES. Likes to program
          computers but is NOT VERY GOOD AT IT.

  Rose  - Has a passion for RATHER OBSCURE LITERATURE. Enjoys creative
          writing and is SOMEWHAT SECRETIVE ABOUT IT.

  Dave  - Has a penchant for spinning out UNBELIEVABLY ILL JAMS with his
          TURNTABLES AND MIXING GEAR. Likes to rave about BANDS NO ONE'S
          EVER HEARD OF BUT HIM.

  Jade  - Has so many INTERESTS, she has trouble keeping track of them all,
          even with an assortment of COLORFUL REMINDERS on her fingers to
          help sort out everything on her mind.

The character position to insert the table at is given by the pos argument. The definitions themselves are given as a slice of 2-tuples of strings, where the first item in each tuple is the term and the second item is the definition. If no definitions are given, or an empty slice is passed in, there will be no output.

The complete maximum width of the table to output including the leading indent is given by the width argument. Note that not every line will be this long; wrapping will often cause them to be shorter.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This function is affected by the following Options:

  • LineSeparator is used to separate each line of the table output.
  • ParagraphSeparator is used to separate each term/definition pair from the other definitions.
  • NoTrailingLineSeparators sets whether to include a trailing LineSeparator at the end of the table. If set to true, it will be omitted, otherwise the table will end with a LineSeparator.
Example

This example produces the table seen above.

ed := Edit("")

var johnDef, roseDef, daveDef, jadeDef string

johnDef += "Has a passion for REALLY TERRIBLE MOVIES. Likes "
johnDef += "to program computers but is NOT VERY GOOD AT IT."

roseDef += "Has a passion for RATHER OBSCURE LITERATURE. "
roseDef += "Enjoys creative writing and is SOMEWHAT "
roseDef += "SECRETIVE ABOUT IT."

daveDef += "Has a penchant for spinning out UNBELIEVABLY ILL "
daveDef += "JAMS with his TURNTABLES AND MIXING GEAR. Likes "
daveDef += "to rave about BANDS NO ONE'S EVER HEARD OF BUT HIM."

jadeDef += "Has so many INTERESTS, she has trouble keeping "
jadeDef += "track of them all, even with an assortment of "
jadeDef += "COLORFUL REMINDERS on her fingers to help sort out "
jadeDef += "everything on her mind."

defs := [][2]string{
	{"John", johnDef},
	{"Rose", roseDef},
	{"Dave", daveDef},
	{"Jade", jadeDef},
}

ed = ed.InsertDefinitionsTable(0, defs, 76)

fmt.Println("TABLE:")
fmt.Println(ed.String())
Output:

TABLE:
  John  - Has a passion for REALLY TERRIBLE MOVIES. Likes to program
          computers but is NOT VERY GOOD AT IT.

  Rose  - Has a passion for RATHER OBSCURE LITERATURE. Enjoys creative
          writing and is SOMEWHAT SECRETIVE ABOUT IT.

  Dave  - Has a penchant for spinning out UNBELIEVABLY ILL JAMS with his
          TURNTABLES AND MIXING GEAR. Likes to rave about BANDS NO ONE'S
          EVER HEARD OF BUT HIM.

  Jade  - Has so many INTERESTS, she has trouble keeping track of them all,
          even with an assortment of COLORFUL REMINDERS on her fingers to
          help sort out everything on her mind.

func (Editor) InsertDefinitionsTableOpts

func (ed Editor) InsertDefinitionsTableOpts(pos int, definitions [][2]string, width int, opts Options) Editor

InsertDefinitionsTableOpts creates a table of term definitions using the provided options and inserts it into the text of the Editor.

This is identical to Editor.InsertDefinitionsTable but provides the ability to set Options for the invocation.

Example

This example uses options to set a custom paragraph separator so that terms are separated by dashes instead of blank lines.

ed := Edit("")

defs := [][2]string{
	{"Apple", "A delicious fruit that can be eaten by pretty much anybody who likes fruit."},
	{"Bottle", "Holds liquids."},
	{"Crow's Egg", "The egg of a crow, who may or may not go CAW-CAW."},
	{"Dog Pinata", "If you hit it, candy will come out."},
}

opts := Options{
	ParagraphSeparator: "\n------------------------------------------------------------\n",
}

ed = ed.InsertDefinitionsTableOpts(0, defs, 60, opts)

fmt.Println("TABLE:")
fmt.Println(ed.String())
Output:

TABLE:
  Apple       - A delicious fruit that can be eaten by
                pretty much anybody who likes fruit.
------------------------------------------------------------
  Bottle      - Holds liquids.
------------------------------------------------------------
  Crow's Egg  - The egg of a crow, who may or may not go
                CAW-CAW.
------------------------------------------------------------
  Dog Pinata  - If you hit it, candy will come out.

func (Editor) InsertTable added in v1.2.0

func (ed Editor) InsertTable(pos int, data [][]string, width int) Editor

InsertTable creates a table from the provided data and inserts it into the text of the Editor.

By default, this will create a table with equally-sized columns spaced out to reach width, with every row of data treated the same. The options TableHeaders, TableBorders, and TableCharSet are all consulted to determine how to draw the table and can be set to customize the table output.

The parameter data is a slice of rows, each of which is a slice of cells of table data. The resulting table will have as many columns as the row in data with the most cells; if any row has fewer than that, an empty string is substituted for the missing cells. If data is empty or nil, no output is produced. If data consists only of empty rows, no output is produced.

This function is affected by the following Options:

  • LineSeparator is used to separate each line of the output.
  • NoTrailingLineSeparators sets whether to include a trailing LineSeparator at the end of the generated table. If set to true, it will be omitted, otherwise the table will end with a LineSeparator. Note that this does not apply to any case where InsertTable output is empty (e.g. if given an empty set of data).
  • TableBorders controls whether the table will have a border.
  • TableHeaders controls whether the first row of data is layed out as headers for the table. They will be separated from the rest of the data with a horizontal rule (or an additional border if TableBorders is also set to true).
  • TableCharSet gives the characters to use to represent lines in the table, used for drawing borders. If TableBorders is enabled, the characters in TableCharSet are used to draw the borders. If TableBorders is disabled but TableHeaders is enabled, the characters in TableCharSet are used to draw the horizontal rule separating the headers from the data.
Example

This example shows the creation of a table from data. Options are used to control the table formatting; see InsertTableOpts examples for a demonstration of this.

data := [][]string{
	{"John", "Egbert", "Heir", "Breath", "Human"},
	{"Kanaya", "Maryam", "Sylph", "Space", "Troll"},
	{"Roxy", "Lalonde", "Rogue", "Void", "Human"},
	{"Rose", "Lalonde", "Seer", "Light", "Human"},
}

const width = 50
const position = 0

ed := Edit("").InsertTable(position, data, width)

fmt.Println(ed.String())
Output:

John        Egbert      Heir      Breath     Human
Kanaya      Maryam      Sylph     Space      Troll
Roxy        Lalonde     Rogue     Void       Human
Rose        Lalonde     Seer      Light      Human

func (Editor) InsertTableOpts added in v1.2.0

func (ed Editor) InsertTableOpts(pos int, data [][]string, width int, opts Options) Editor

InsertTableOpts creates a table from the provided data using the provided options and inserts it into the text of the Editor.

This is identical to Editor.InsertTable but provides the ability to set Options for the invocation.

Example (TableBorders)

This example shows the use of the TableBorders option to add a border to table output.

data := [][]string{
	{"John", "Egbert", "Heir", "Breath", "Human"},
	{"Kanaya", "Maryam", "Sylph", "Space", "Troll"},
	{"Wayward", "Vagabond", "", "", "Carapacian"},
	{"Roxy", "Lalonde", "Rogue", "Void", "Human"},
	{"Rose", "Lalonde", "Seer", "Light", "Human"},
	{"Caliborn", "", "Lord", "Time", "Cherub"},
}

opts := Options{
	TableBorders: true,
}

const width = 65
const position = 0

ed := Edit("").InsertTableOpts(position, data, width, opts)

fmt.Println(ed.String())
Output:

+-------------+-------------+---------+----------+--------------+
| John        | Egbert      | Heir    | Breath   | Human        |
| Kanaya      | Maryam      | Sylph   | Space    | Troll        |
| Wayward     | Vagabond    |         |          | Carapacian   |
| Roxy        | Lalonde     | Rogue   | Void     | Human        |
| Rose        | Lalonde     | Seer    | Light    | Human        |
| Caliborn    |             | Lord    | Time     | Cherub       |
+-------------+-------------+---------+----------+--------------+
Example (TableBordersAndHeaders)

This example shows how the TableHeaders and TableBorders options can be combined to both add a border and create a header from the first row in the data. The result is slightly different from just setting TableBorders, as when both are set, the headers will be centered within their cells and the header break line is changed to the TableBorders-style horizontal rule.

data := [][]string{
	{"Name", "Surname", "Class", "Aspect", "Species"},
	{"John", "Egbert", "Heir", "Breath", "Human"},
	{"Kanaya", "Maryam", "Sylph", "Space", "Troll"},
	{"Wayward", "Vagabond", "", "", "Carapacian"},
	{"Roxy", "Lalonde", "Rogue", "Void", "Human"},
	{"Rose", "Lalonde", "Seer", "Light", "Human"},
	{"Caliborn", "", "Lord", "Time", "Cherub"},
}

opts := Options{
	TableBorders: true,
	TableHeaders: true,
}

const width = 65
const position = 0

ed := Edit("").InsertTableOpts(position, data, width, opts)

fmt.Println(ed.String())
Output:

+-------------+-------------+---------+----------+--------------+
|     NAME    |   SURNAME   |  CLASS  |  ASPECT  |    SPECIES   |
+-------------+-------------+---------+----------+--------------+
| John        | Egbert      | Heir    | Breath   | Human        |
| Kanaya      | Maryam      | Sylph   | Space    | Troll        |
| Wayward     | Vagabond    |         |          | Carapacian   |
| Roxy        | Lalonde     | Rogue   | Void     | Human        |
| Rose        | Lalonde     | Seer    | Light    | Human        |
| Caliborn    |             | Lord    | Time     | Cherub       |
+-------------+-------------+---------+----------+--------------+
Example (TableCharSet)

This example shows how to override the character set used to draw table borders and horizontal rules within the table.

borderData := [][]string{
	{"John", "Egbert", "Heir", "Breath", "Human"},
	{"Kanaya", "Maryam", "Sylph", "Space", "Troll"},
	{"Wayward", "Vagabond", "", "", "Carapacian"},
	{"Roxy", "Lalonde", "Rogue", "Void", "Human"},
	{"Rose", "Lalonde", "Seer", "Light", "Human"},
	{"Caliborn", "", "Lord", "Time", "Cherub"},
}

opts := Options{
	TableBorders: true,

	// First character is used for corners/intersections
	// Second character is used for vertical lines
	// Third character is used for horizontal lines
	TableCharSet: "#*=",
}

const width = 65
const position = 0

edBorders := Edit("").InsertTableOpts(position, borderData, width, opts)

fmt.Println("With borders:")
fmt.Println()
fmt.Println(edBorders.String())

// you can also do this for headers only; in this case, the third character
// of TableCharSet is used for drawing the horizontal rule:

smallDataSet := [][]string{
	{"Surname", "Name"},
	{"Egbert", "John"},
	{"Lalonde", "Rose"},
}

// use a smaller width for this one, it's a smaller table
const smallTableWidth = 15

opts = opts.WithTableBorders(false).WithTableHeaders(true)
edHeaders := Edit("").InsertTableOpts(position, smallDataSet, smallTableWidth, opts)

fmt.Println("With headers:")
fmt.Println()
fmt.Println(edHeaders.String())
Output:

With borders:

#=============#=============#=========#==========#==============#
* John        * Egbert      * Heir    * Breath   * Human        *
* Kanaya      * Maryam      * Sylph   * Space    * Troll        *
* Wayward     * Vagabond    *         *          * Carapacian   *
* Roxy        * Lalonde     * Rogue   * Void     * Human        *
* Rose        * Lalonde     * Seer    * Light    * Human        *
* Caliborn    *             * Lord    * Time     * Cherub       *
#=============#=============#=========#==========#==============#

With headers:

SURNAME    NAME
===============
Egbert     John
Lalonde    Rose
Example (TableHeaders)

This example shows the use of the TableHeaders option to set the first row of data apart from the rest with a horizontal rule and to format the first row of data as a header row.

data := [][]string{
	{"Aspect", "Class", "Surname", "Name"},
	{"Breath", "Heir", "Egbert", "John"},
	{"Light", "Seer", "Lalonde", "Rose"},
	{"Time", "Knight", "Strider", "Dave"},
	{"Space", "Witch", "Harley", "Jade"},
	{"Void", "Rogue", "Lalonde", "Roxy"},
	{"Heart", "Prince", "Strider", "Dirk"},
	{"Hope", "Page", "English", "Jake"},
	{"Life", "Maid", "Crocker", "Jane"},
}

opts := Options{
	TableHeaders: true,
}

const width = 65
const position = 0

ed := Edit("").InsertTableOpts(position, data, width, opts)

fmt.Println(ed.String())
Output:

ASPECT              CLASS               SURNAME              NAME
-----------------------------------------------------------------
Breath              Heir                Egbert               John
Light               Seer                Lalonde              Rose
Time                Knight              Strider              Dave
Space               Witch               Harley               Jade
Void                Rogue               Lalonde              Roxy
Heart               Prince              Strider              Dirk
Hope                Page                English              Jake
Life                Maid                Crocker              Jane

func (Editor) InsertTwoColumns

func (ed Editor) InsertTwoColumns(pos int, leftText string, rightText string, minSpaceBetween int, width int, leftColPercent float64) Editor

InsertTwoColumns builds a two-column layout of side-by-side text from two sequences of text and inserts it into the text of the Editor. The leftText and the rightText do not need any special preparation to be used as the body of each column, as they will be automatically wrapped to fit.

This function has several parameters:

  • pos gives the position to insert the columns at within the Editor.
  • leftText is the text to put into the left column.
  • rightText is the text to put into the right column.
  • minSpaceBetween is the amount of space between the two columns at the left column's widest possible length.
  • width is how much horizontal space the two columns along with the space between them should be wrapped to.
  • leftColPercent is a number from 0.0 to 1.0 that gives how much of the available width (width - minSpaceBetween) the left column should take up. The right column will infer its width from what remains. If leftColPercent is less than 0.0, it will be assumed to be 0.0. If greater than 1.0, it will be assumed to be 1.0.

The minimum width that a column can be is always 2 characters wide.

If the left column ends up taking more vertical space than the right column, the left column will have spaces added on subsequent lines to meet with where the right column would have started if it had had more lines.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This function is affected by the following Options:

  • LineSeparator is used to separate each line of the output.
  • NoTrailingLineSeparators sets whether to include a trailing LineSeparator at the end of the generated columns. If set to true, it will be omitted, otherwise the columns will end with a LineSeparator.
Example

This example creates two columns from two runs of text.

leftText := "Karkalicious, definition: makes Terezi loco. "
leftText += "She wants to know the secrets that she can't "
leftText += "taste in my photo."

rightText := "A young man stands in his bedroom. It just so happens that "
rightText += "today, the 13th of April, 2009, is this young man's birthday. "
rightText += "Though it was thirteen years ago he was given life, it is "
rightText += "only today he will be given a name!"

// insert it at the start of the editor
pos := 0

// minimum 3 spaces between each column at their closest point
minSpace := 3

// wrap the entire layout to 50 chars
width := 50

// make the left column take up 40% of the available space
leftPercent := 0.4

ed := Edit("").InsertTwoColumns(pos, leftText, rightText, minSpace, width, leftPercent)

fmt.Println(ed.String())
Output:

Karkalicious,        A young man stands in his
definition: makes    bedroom. It just so happens
Terezi loco. She     that today, the 13th of
wants to know the    April, 2009, is this young
secrets that she     man's birthday. Though it was
can't taste in my    thirteen years ago he was
photo.               given life, it is only today
                     he will be given a name!

func (Editor) InsertTwoColumnsOpts

func (ed Editor) InsertTwoColumnsOpts(pos int, leftText string, rightText string, minSpaceBetween int, width int, leftColPercent float64, opts Options) Editor

InsertTwoColumnsOpts builds a two-column layout of side-by-side text from two sequences of text using the options provided and inserts it into the text of the Editor.

This is identical to Editor.InsertTwoColumns but provides the ability to set Options for the invocation.

Example

This example uses options to tell the Editor to include the HTML tag "<br/>" followed by a literal newline to separate lines in the output columns.

left := "A sample short text run that wraps once."
right := "This run of text should also take up 2 lines."

pos := 0
minSpace := 3
width := 50
leftPercent := 0.5

opts := Options{
	LineSeparator: "<br/>\n",
}

ed := Edit("").InsertTwoColumnsOpts(pos, left, right, minSpace, width, leftPercent, opts)

fmt.Println(ed.String())
Output:

A sample short text run   This run of text should<br/>
that wraps once.          also take up 2 lines.<br/>

func (Editor) IsSubEditor

func (ed Editor) IsSubEditor() bool

IsSubEditor returns whether the Editor was created to edit a sub-set of the text in some parent editor. Calls to Editor.Lines, Editor.LinesFrom, Editor.LinesTo, Editor.Chars, Editor.CharsFrom, and Editor.CharsTo will result in such an Editor.

If IsSubEditor returns true, then Editor.Text may be set to an incomplete subset of the original text. To get the full text from a sub-editor, use Editor.CommitAll to get the root parent editor with all sub-editor changes applied, including the ones in this sub-editor.

This is a Sub-Editor function. See the note on Editor for more info.

Example

This example shows that only Editors created from a sub-editor producing function will return true for IsSubEditor.

notASubEd := Edit("Hello, world!")
subEd := Edit("Sub, Sburb?").CharsFrom(5)

fmt.Println(notASubEd.IsSubEditor())
fmt.Println(subEd.IsSubEditor())
Output:

false
true

func (Editor) Justify

func (ed Editor) Justify(width int) Editor

Justify edits the whitespace in each line of the Editor's text such that all words are spaced approximately equally and the line as a whole spans the given width.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This function is affected by the following Options:

  • JustifyLastLine is used to determine whether the last line of input text will be justified. The default behavior with JustifyLastLine set to false is to leave the last line alone. If the option is set to true, the last line is justified the same way as every other line. Note that if JustifyLastLine is set to false, Justify will have no effect on an Editor with only 1 line of text.
  • LineSeparator is used to separate lines of input.
  • ParagraphSeparator is the separator used to split paragraphs. It will only have effect if PreserveParagraphs is set to true.
  • PreserveParagraphs gives whether to respect paragraphs instead of considering them text to be justified. If set to true, the text is split into paragraphs by ParagraphSeparator, then the justify is applied to each paragraph.
  • NoTrailingLineSeparators specifies whether the function should consider a final instance of LineSeparator to be ending the prior line or giving the start of a new line. If NoTrailingLineSeparators is true, a trailing LineSeparator is considered to start a new (empty) line; additionally, the justify will be called at least once for an empty string. If NoTrailingLineSeparators is set to false and the Editor text is set to an empty string, the justify will not be called even once.
Example

This example shows justification of input text to a length of 60 chars.

input := "Some words that will have spacing justified.\n"
input += "This occurs on a per-line basis.\n"
input += "Lines closer to the justified length have less adjustment.\n"
input += "By default the last line is unmodified."

ed := Edit(input)

ed = ed.Justify(60)

fmt.Println(ed.String())
Output:

Some    words   that    will    have   spacing    justified.
This      occurs       on       a      per-line       basis.
Lines  closer to the justified length have less  adjustment.
By default the last line is unmodified.

func (Editor) JustifyOpts

func (ed Editor) JustifyOpts(width int, opts Options) Editor

JustifyOpts edits the whitespace in each line of the Editor's text such that all words are spaced approximately equally and the line as a whole spans the given width using the provided options.

This is identical to Editor.Justify but provides the ability to set Options for the invocation.

Example (JustifyLastLine)

This example shows the use of options to force the last line to be justified.

input := "Your name is GAMZEE MAKARA. You get pretty\n"
input += "excited by CLOWNS OF A GRIM PERSUASION WHICH MAY\n"
input += "NOT BE IN FULL POSSESSION OF THEIR MENTAL\n"
input += "FACULTIES. You belong to a RATHER OBSCURE CULT,\n"
input += "which foretells of a BAND OF ROWDY AND CAPRICIOUS\n"
input += "MINSTRELS which will rise one day on a MYTHICAL\n"
input += "PARADISE PLANET that does not exist yet."

opts := Options{
	JustifyLastLine: true,
}

ed := Edit(input)

ed = ed.JustifyOpts(50, opts)

fmt.Println(ed.String())
Output:

Your  name  is  GAMZEE  MAKARA.  You  get   pretty
excited  by CLOWNS OF A GRIM PERSUASION WHICH  MAY
NOT  BE   IN  FULL  POSSESSION  OF  THEIR   MENTAL
FACULTIES.  You belong  to a RATHER  OBSCURE CULT,
which  foretells of a BAND OF ROWDY AND CAPRICIOUS
MINSTRELS  which will  rise one day on a  MYTHICAL
PARADISE   PLANET  that   does   not  exist   yet.
Example (ParagraphSeparator)

This example shows the use of options to make the justification respect a rather contrived paragraph splitter of "\nPARA SPLIT\n"

input := "Some words that will have spacing justified.\n"
input += "This occurs on a per-line basis.\n"
input += "Lines closer to the justified length have less adjustment.\n"
input += "By default the last line is unmodified.\n"
input += "PARA SPLIT\n"
input += "This is a second paragraph that is used to show how\n"
input += "paragraphs can be respected with options.\n"
input += "And the last paragraph line is still unmodified.\n"

opts := Options{
	PreserveParagraphs: true,
	ParagraphSeparator: "\nPARA SPLIT\n",
}

ed := Edit(input)

ed = ed.JustifyOpts(60, opts)

fmt.Println(ed.String())
Output:

Some    words   that    will    have   spacing    justified.
This      occurs       on       a      per-line       basis.
Lines  closer to the justified length have less  adjustment.
By default the last line is unmodified.
PARA SPLIT
This  is a  second  paragraph  that  is  used  to  show  how
paragraphs    can     be     respected     with     options.
And the last paragraph line is still unmodified.

func (Editor) LineCount

func (ed Editor) LineCount() int

LineCount returns the number of lines in the Editor's text. Lines are considered to be split by the currently set LineSeparator in the Editor's Options property; if one has not yet been set, DefaultLineSeparator is used.

By default, an Editor whose text is set to the empty string will cause this function to return 0. This can be altered with the options set on the Editor.

This function is affected by the following Options:

  • LineSeparator is used to determine what splits lines to be counted.
  • NoTrailingLineSeparators sets whether a trailing LineSeparator should be expected in a full line. If set to true, it will consider the empty string to be a non-terminated line as opposed to 0 lines.
Example

This example shows querying the number of lines for a variety of text.

zeroLinesEd := Edit("")
fmt.Println(zeroLinesEd.LineCount())

oneLineEd := Edit("Line 1")
fmt.Println(oneLineEd.LineCount())

twoLinesEd := Edit("Line 1\nLine 2")
fmt.Println(twoLinesEd.LineCount())
Output:

0
1
2
Example (Options)

This example shows the two different ways currently-set options affect the output of LineCount. In the first case, it shows how NoTrailingLineSeparators affects the number of lines in an empty editor, and the in the second case, it shows the use of a custom LineSeparator to change how lines are counted.

noTrailingOpts := Options{NoTrailingLineSeparators: true}
emptyButNoTrailingEd := Edit("").WithOptions(noTrailingOpts)
fmt.Println(emptyButNoTrailingEd.LineCount())

customLineOpts := Options{LineSeparator: "<br/>"}
customLineEndEd := Edit("Line 1<br/>Line 2").WithOptions(customLineOpts)
fmt.Println(customLineEndEd.LineCount())
Output:

1
2

func (Editor) Lines

func (ed Editor) Lines(start, end int) Editor

Lines produces an Editor to operate on a subset of the lines in the Editor's text. The returned Editor operates on text from the nth line up to (but not including) the ith line, where n is start and i is end.

The start or end parameter may be negative, in which case it will be relative to the end of the text; -1 would be the index of the last line, -2 would be the index of the second-to-last line, etc.

If one of the parameters specifies an index that is past the end of the string, that index is assumed to be the end of the string. If either specify an index that is before the start of the string, it is assumed to be 0.

If end is less than start, it is assumed to be equal to start.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This is a Sub-Editor function. See the note on Editor for more info.

This function is affected by the following Options:

  • LineSeparator specifies what string should be used to delimit lines.
  • NoTrailingLineSeparators specifies whether the function should consider a trailing instance of LineSeparator to end the prior line, or to start a new line. If NoTrailingLineSeparators is true, a trailing LineSeparator is considered to start a new (empty) line.
Example

This example gets a subeditor on the middle three lines of a five-line string.

ed := Edit("Act 1\nAct 2\nAct 3\nAct 4\nAct 5")

ed = ed.Lines(1, 4)

// Not doing Editor.String for the example because that would call Commit
// and get back the starting string.
fmt.Println(ed.Text)
Output:

Act 2
Act 3
Act 4

func (Editor) LinesFrom

func (ed Editor) LinesFrom(start int) Editor

LinesFrom produces an Editor to operate on a subset of the lines in the Editor's text. The returned Editor operates on text from the nth line up to the end of the text, where n is start.

Calling this function is identical to calling Editor.Lines with the given start and with end set to the end of the text.

Example

This example gets a subeditor on the last two lines of a five-line string.

ed := Edit("Act 1\nAct 2\nAct 3\nAct 4\nAct 5")

ed = ed.LinesFrom(3)

// Not doing Editor.String for the example because that would call Commit
// and get back the starting string.
fmt.Println(ed.Text)
Output:

Act 4
Act 5

func (Editor) LinesTo

func (ed Editor) LinesTo(end int) Editor

LinesTo produces an Editor to operate on a subset of the characters in the Editor's text. The returned Editor operates on text from the first line up to but not including the nth line, where n is end.

Calling this function is identical to calling Editor.Lines with the given end and with start set to the start of the text.

Example

This example gets a subeditor on the first three lines of a five-line string.

ed := Edit("Act 1\nAct 2\nAct 3\nAct 4\nAct 5")

ed = ed.LinesTo(3)

// Not doing Editor.String for the example because that would call Commit
// and get back the starting string.
fmt.Println(ed.Text)
Output:

Act 1
Act 2
Act 3

func (Editor) Overtype added in v0.2.0

func (ed Editor) Overtype(charPos int, text string) Editor

Overtype adds characters at the given position, writing over any that already exist. If the added text would extend beyond the current end of the Editor text, the Editor text is extended to make room for it.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

Example

This example uses Overtype to replace a part of a greeting message. This works so nicely in the example because the replacement is the exact same length as the replaced text. If it were of a longer length, it would end up overwriting the text that comes after the replaced text; if it were shorter, it would end up not replacing the entire intended section.

ed := Edit("How are you, Miss Lalonde?")

ed = ed.Overtype(4, "goes it")

fmt.Println(ed.String())
Output:

How goes it, Miss Lalonde?

func (Editor) String

func (ed Editor) String() string

String returns the finished, fully edited string. If the Editor is a sub-editor, CommitAll() is called first and Editor.Text from the resulting editor is returned; otherwise, the current Editor's Text is returned.

Example

This example uses String on a normal Editor to get its text.

ed := Edit("Some text")

text := ed.String()

fmt.Println(text)
Output:

Some text
Example (SubEditor)

This example uses String on a sub-editor to avoid having to explicitly call Editor.Commit or Editor.CommitAll after performing an operation on the sub-editor.

ed := Edit("Act 1\nAct 2\nAct BLAH\nAct 4\nAct 5")
subEd := ed.Lines(2, 3)

subEd = subEd.Delete(4, 8).Insert(4, "3")

fullText := subEd.String()
fmt.Println(fullText)
Output:

Act 1
Act 2
Act 3
Act 4
Act 5

func (Editor) WithOptions

func (ed Editor) WithOptions(opts Options) Editor

WithOptions returns an Editor identical to the current one but with its Options replaced by the given Options. This does not affect the Editor it was called on.

This function has the same effect as manually setting Editor.Options but provides a way to do so in a fluent convention.

To get a set of Options that are identical to the current ones but with a single item changed, get the Editor's current Options value and call one of the Options.WithX functions on it.

Example

This example sets the IndentStr property of the Options on the Editor.

ed := Edit("Vriska Serket")

ed = ed.WithOptions(ed.Options.WithIndentStr("-->"))

fmt.Println(ed.Options.IndentStr)
Output:

-->

func (Editor) Wrap

func (ed Editor) Wrap(width int) Editor

Wrap wraps the Editor text to the given width. All runs of whitespace are collapsed automatically prior to the wrap.

If width is less than 2, it is assumed to be 2 because no meaningful wrap algorithm can be applied to anything smaller.

This function is grapheme-aware and indexes text by human-readable characters, not by the bytes or runes that make it up. See the note on Grapheme-Awareness in the rosed package docs for more info.

This function is affected by the following Options:

  • LineSeparator is placed at the end of each wrapped line. In addition, any sequence of LineSeparator that exists in the text prior to calling this function will be treated as whitespace and collapsed into a single space character.
  • ParagraphSeparator is the separator used to split paragraphs. It will only have effect if PreserveParagraphs is set to true.
  • PreserveParagraphs gives whether to respect paragraphs instead of considering them text to be wrapped. If set to true, the text is first split into paragraphs by ParagraphSeparator, then the wrap is applied to each paragraph.
Example

This example shows wrapping applied to a long string.

ed := Edit("Your name is VRISKA SERKET. You are a master of EXTREME ROLEPLAYING.")

ed = ed.Wrap(25)

fmt.Println(ed.String())
Output:

Your name is VRISKA
SERKET. You are a master
of EXTREME ROLEPLAYING.

func (Editor) WrapOpts

func (ed Editor) WrapOpts(width int, opts Options) Editor

WrapOpts wraps the Editor text to the given width using the supplied options.

This is identical to Editor.Wrap but provides the ability to set Options for the invocation.

Example

This example uses options to tell the wrap to use a line ending consisting of the HTML tag "<br/>" followed by a new-line, and to respect paragraphs separated by a double "<br/>\n". It also shows how pre-wrapped text will have the hard wraps removed prior to the new wrap.

text := "Your name is VRISKA SERKET.<br/>\n"
text += "<br/>\n"
text += "You are a master of EXTREME ROLEPLAYING. You can't get enough<br/>\n"
text += "of it, or really any game of high stakes and chance. You have<br/>\n"
text += "persisted with the habit even in spite of your ACCIDENT. But<br/>\n"
text += "then again, you don't have much choice.<br/>\n"
text += "<br/>\n"
text += "You are something of an APOCALYPSE BUFF, which is something you<br/>\n"
text += "can be on Alternia. You are fascinated by end of the world<br/>\n"
text += "scenarios."

opts := Options{
	LineSeparator:      "<br/>\n",
	ParagraphSeparator: "<br/>\n<br/>\n",
	PreserveParagraphs: true,
}

ed := Edit(text)

ed = ed.WrapOpts(50, opts)

fmt.Println(ed.String())
Output:

Your name is VRISKA SERKET.<br/>
<br/>
You are a master of EXTREME ROLEPLAYING. You can't<br/>
get enough of it, or really any game of high<br/>
stakes and chance. You have persisted with the<br/>
habit even in spite of your ACCIDENT. But then<br/>
again, you don't have much choice.<br/>
<br/>
You are something of an APOCALYPSE BUFF, which is<br/>
something you can be on Alternia. You are<br/>
fascinated by end of the world scenarios.

type LineOperation

type LineOperation func(idx int, line string) []string

LineOperation is a function that accepts a zero-indexed line number and the contents of that line and performs some operation to produce zero or more new lines to replace the contents of the line with.

The return value for a LineOperation is a slice of lines to insert at the old line position. This can be used to delete the line or insert additional new ones; to insert, include the new lines in the returned slice in the proper position relative to the old line in the slice, and to delete the original line, a slice with len < 1 can be returned.

The parameter idx will always be the index of the line before any transformations were applied; e.g. if used in Editor.Apply, a call to a LineOperation with idx = 4 will always be after a call with idx = 3, regardless of the size of the returned slice in the prior call.

type Options

type Options struct {
	// IndentStr is the string that is used for a single horizontal indent. If
	// this is set to "", it will be interpreted as though it were set to
	// DefaultIndentString.
	IndentStr string

	// LineSeparator is the string that the Editor considers to signify the
	// end of a line. If this is set to "", it will be interpreted as though it
	// were set to DefaultLineSeparator.
	LineSeparator string

	// NoTrailingLineSeparators is whether the Editor considers lines to not end
	// with the separator, and thus would assume that a properly formatted line
	// does not include a line separator at the end even if it is the last line.
	//
	// This has a variety of effects depending on the function that is being
	// called on an Editor; functions that are affected will call it out in
	// their documentation.
	//
	// If this is set to false (the default), line separator chars are assumed
	// to signify the end of the line.
	NoTrailingLineSeparators bool

	// ParagraphSeparator is the sequence that is considered to separate
	// paragraphs in the text. Paragraphs are considered to have separators
	// rather than terminators; i.e. this sequence does not occur at the start
	// of the first paragraph or at the end of the final paragraph. It may or
	// may not include LineSeparator as a substring; every mode of operation of
	// Editor is intended to transparantly handle this case.
	//
	// If this is set to "", it will be interpreted as though it were set to
	// DefaultParagraphSeparator.
	ParagraphSeparator string

	// PreserveParagraphs says whether operations that adjust separator
	// characters (such as wrap) should preserve paragraphs and their
	// separators. If not set, certain operations may modify paragraph
	// separators.
	PreserveParagraphs bool

	// JustifyLastLine gives whether text justify operations should apply to
	// the last line of a block of text (or paragraph if PreserveParagraphs is
	// set to true and is respected). Conventionally, justifications are not
	// applied to the last line of a block of text and this is the default
	// behavior.
	JustifyLastLine bool

	// TableBorders sets whether created tables draw cell and table borders on
	// them.
	TableBorders bool

	// TableHeaders sets whether table creation functions should consider the
	// first row of data to be data headers. If so, they will be layed out
	// separately from the other data and separated by a horizontal rule.
	TableHeaders bool

	// TableCharSet is the set of characters used to draw tables. It can be up
	// to 3 characters long. The first char is used to draw table corners and
	// line crossings, the second char is used to draw vertical lines, and the
	// third char is used to draw horizontal lines.
	//
	// Any characters beyond the first three are ignored.
	//
	// If this is set to "", it will be interpreted as though it were set to
	// DefaultTableCharSet. If it is set to less than three characters, the
	// missing characters up to 3 will be filled in from the remaining chars in
	// DefaultTableCharSet; e.g. setting TableCharSet to "#" will result in an
	// interpreted TableCharSet of "#|-".
	TableCharSet string
}

Options control the behavior of an Editor. The zero-value is an Options with all members set to defaults.

IndentStr, LineSeparator, ParagraphSeparator, and TableChars have special behavior if not set manually. In a zero-valued Options, each one will be the empty string. When interpreting the options in the course of performing an operation, functions that use those values will treat an empty string as DefaultIndentString, DefaultLineSeparator, DefaultParagraphSeparator, or [DefaultTableChars] respectively.

func (Options) String

func (opts Options) String() string

String gets the string representation of the Options.

Example
opts := Options{IndentStr: "-->"}

str := opts.String()

fmt.Println(str)
Output:

Options{ParagraphSeparator: "", LineSeparator: "", IndentStr: "-->", NoTrailingLineSeparators: false, PreserveParagraphs: false, JustifyLastLine: false, TableBorders: false, TableHeaders: false, TableCharSet: ""}

func (Options) WithDefaults

func (opts Options) WithDefaults() Options

WithDefaults returns a copy of the options with all blank members filled with their defaults. Internally, this function is used on user-provided Options structs in order to get ready-to-use copies.

This function does not modify the Options it is called on.

Example

This example shows how WithDefaults can be called to set all currently unset properties to their default values while leaving the set values alone.

opts := Options{
	LineSeparator:      "<br/>",
	PreserveParagraphs: true,
	TableCharSet:       "#",
}

optsDefault := opts.WithDefaults()
fmt.Printf("IndentStr: %q\n", optsDefault.IndentStr)
fmt.Printf("LineSeparator: %q\n", optsDefault.LineSeparator)
fmt.Printf("PreserveParagraphs: %v\n", optsDefault.PreserveParagraphs)

// TableCharSet, instead of being all or nothing for settedness, allows the
// user to specify fewer characters than are required. If that is the case,
// as it was above, additional characters are added to it on a call to
// WithDefaults to make a complete char set
fmt.Printf("TableCharSet: %q\n", optsDefault.TableCharSet)
Output:

IndentStr: "\t"
LineSeparator: "<br/>"
PreserveParagraphs: true
TableCharSet: "#|-"

func (Options) WithIndentStr

func (opts Options) WithIndentStr(str string) Options

WithIndentStr returns a new Options identical to this one but with IndentStr set to str. If str is the empty string, the indent str is interpreted as DefaultIndentString.

This function does not modify the Options it is called on.

Example
opts := Options{
	IndentStr: "",
}

opts = opts.WithIndentStr("-->")

fmt.Println(opts.IndentStr)
Output:

-->

func (Options) WithJustifyLastLine added in v1.1.0

func (opts Options) WithJustifyLastLine(justifyLastLine bool) Options

WithJustifyLastLine returns a new Options identical to this one but with JustifyLastLine set to justifyLastLine.

This function does not modify the Options it is called on.

Example
opts := Options{
	JustifyLastLine: false,
}

opts = opts.WithJustifyLastLine(true)

fmt.Println(opts.JustifyLastLine)
Output:

true

func (Options) WithLineSeparator

func (opts Options) WithLineSeparator(sep string) Options

WithLineSeparator returns a new Options identical to this one but with the LineSeparator member set to sep. If sep is the empty string, the line separator is interpreted as DefaultLineSeparator.

This function does not modify the Options it is called on.

Example
opts := Options{
	LineSeparator: "\n",
}

opts = opts.WithLineSeparator("<br/>")

fmt.Println(opts.LineSeparator)
Output:

<br/>

func (Options) WithNoTrailingLineSeparators

func (opts Options) WithNoTrailingLineSeparators(noTrailingLineSeps bool) Options

WithNoTrailingLineSeparators returns a new Options identical to this one but with NoTrailingLineSeparators set to noTrailingLineSeps.

This function does not modify the Options it is called on.

Example
opts := Options{
	NoTrailingLineSeparators: false,
}

opts = opts.WithNoTrailingLineSeparators(true)

fmt.Println(opts.NoTrailingLineSeparators)
Output:

true

func (Options) WithParagraphSeparator

func (opts Options) WithParagraphSeparator(sep string) Options

WithParagraphSeparator returns a new Options identical to this one but with ParagraphSeparator set to sep. If sep is the empty string, the paragraph separator is interpreted as DefaultParagraphSeparator.

This function does not modify the Options it is called on.

Example
opts := Options{
	ParagraphSeparator: "\n\n",
}

opts = opts.WithParagraphSeparator("<P>")

fmt.Println(opts.ParagraphSeparator)
Output:

<P>

func (Options) WithPreserveParagraphs

func (opts Options) WithPreserveParagraphs(preserve bool) Options

WithPreserveParagraphs returns a new Options identical to this one but with PreserveParagraphs set to preserve.

This function does not modify the Options it is called on.

Example
opts := Options{
	PreserveParagraphs: false,
}

opts = opts.WithPreserveParagraphs(true)

fmt.Println(opts.PreserveParagraphs)
Output:

true

func (Options) WithTableBorders added in v1.2.0

func (opts Options) WithTableBorders(borders bool) Options

WithTableBorders returns a new Options identical to this one but with TableBorders set to borders.

This function does not modify the Options it is called on.

Example
opts := Options{
	TableBorders: false,
}

opts = opts.WithTableBorders(true)

fmt.Println(opts.TableBorders)
Output:

true

func (Options) WithTableCharSet added in v1.2.0

func (opts Options) WithTableCharSet(charSet string) Options

WithTableCharSet returns a new Options identical to this one but with TableCharSet set to charSet.

This function does not modify the Options it is called on.

Example
opts := Options{
	TableCharSet: "123",
}

opts = opts.WithTableCharSet("@IK")

fmt.Println(opts.TableCharSet)
Output:

@IK

func (Options) WithTableHeaders added in v1.2.0

func (opts Options) WithTableHeaders(headers bool) Options

WithTableHeaders returns a new Options identical to this one but with TableHeaders set to headers.

This function does not modify the Options it is called on.

Example
opts := Options{
	TableHeaders: false,
}

opts = opts.WithTableHeaders(true)

fmt.Println(opts.TableHeaders)
Output:

true

type ParagraphOperation

type ParagraphOperation func(idx int, para, sepPrefix, sepSuffix string) []string

ParagraphOperation is a function that accepts a zero-indexed paragraph number and the contents of that paragraph and performs some operation to produce zero or more new paragraphs to replace the contents of the paragraph with.

The return value for a ParagraphOperation is a slice of paragraphs to insert at the old paragraph position. This can be used to delete the paragraph or insert additional new ones; to insert, include the new paragraph in the returned slice in the proper position relative to the old paragraph in the slice; to delete the original paragraph, a slice with len < 1 can be returned.

The parameter idx will always be the index of the paragraph before any transformations were applied; e.g. if used in Editor.ApplyParagraphs, a call to a ParagraphOperation with idx = 4 will always be after a call with idx = 3, regardless of the size of the returned slice in the prior call.

The paragraphs may have additional contents at the beginning and end as part of the currently defined ParagraphSeparator. In this case, such content that would come at the start of the paragraph is provided in sepPrefix, and such content that would come at the end of the paragraph is provied in sepSuffix. These values are provided for reference within a ParagraphOperation, but a ParagraphOperation should assume the caller of it will automatically add the separators (which will include the affixes) as needed to the returned paragraph(s).

Directories

Path Synopsis
internal
gem
Package gem provides operations for individual user-perceived characters and implements UAX #29 by Unicode for grapheme boundary finding.
Package gem provides operations for individual user-perceived characters and implements UAX #29 by Unicode for grapheme boundary finding.
manip
Package manip contains manipulation primitives used by Editor functions to manipulate text.
Package manip contains manipulation primitives used by Editor functions to manipulate text.
tb
Package tb holds the primitives for working with blocks of gem.String text.
Package tb holds the primitives for working with blocks of gem.String text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL