Documentation
¶
Overview ¶
Package buffer contiene el modelo de texto de FlowCode.
De momento solo la parte de lectura: cargar un archivo y saber dónde empieza cada línea. La estructura editable —el rope con transacciones, deshacer y múltiples cursores— llega en el Hito 3.
El paquete no depende de nada gráfico a propósito. Abrir un archivo, indexar sus líneas y consultarlas debe poder probarse sin abrir una ventana.
Index ¶
- Variables
- func Transform(offset int, bias Bias, edits []Edit) int
- type Bias
- type EOL
- type Edit
- type Encoding
- type File
- func (f *File) Close() error
- func (f *File) IndexDuration() int64
- func (f *File) Indexing() bool
- func (f *File) Len() int
- func (f *File) Line(i int) string
- func (f *File) LineCount() int
- func (f *File) LineEnd(line int) int
- func (f *File) LineOf(offset int) (line, start int)
- func (f *File) LineStart(line int) int
- func (f *File) Mapped() bool
- func (f *File) Path() string
- func (f *File) Size() int
- func (f *File) Slice(start, end int) string
- type History
- type Rope
- func (r *Rope) Delete(start, end int)
- func (r *Rope) Insert(offset int, s string)
- func (r *Rope) Len() int
- func (r *Rope) Line(line int) string
- func (r *Rope) LineCount() int
- func (r *Rope) LineEnd(line int) int
- func (r *Rope) LineOf(offset int) (line, start int)
- func (r *Rope) LineStart(line int) int
- func (r *Rope) Offset(line, col int) int
- func (r *Rope) Slice(start, end int) string
- func (r *Rope) String() string
- type Selection
- type Selections
- func (s *Selections) Add(sel Selection)
- func (s *Selections) All() []Selection
- func (s *Selections) Clone() []Selection
- func (s *Selections) Len() int
- func (s *Selections) Primary() Selection
- func (s *Selections) Replace(list []Selection)
- func (s *Selections) Set(sel Selection)
- func (s *Selections) Transform(edits []Edit)
- func (s *Selections) Update(f func(Selection) Selection)
- type Stat
- type Text
- func (t *Text) Apply(edits []Edit, sel *Selections) error
- func (t *Text) Bytes() []byte
- func (t *Text) Dirty() bool
- func (t *Text) EOL() EOL
- func (t *Text) Encoding() Encoding
- func (t *Text) History() *History
- func (t *Text) Len() int
- func (t *Text) Line(i int) string
- func (t *Text) LineCount() int
- func (t *Text) LineEnd(line int) int
- func (t *Text) LineOf(offset int) (line, start int)
- func (t *Text) LineStart(line int) int
- func (t *Text) Offset(line, col int) int
- func (t *Text) Path() string
- func (t *Text) Position(offset int) (line, col int)
- func (t *Text) Redo(sel *Selections) bool
- func (t *Text) Reload(sel *Selections) error
- func (t *Text) Revision() int
- func (t *Text) Rope() *Rope
- func (t *Text) Save() error
- func (t *Text) SaveOverwrite() error
- func (t *Text) SetPath(path string)
- func (t *Text) SetStat(s Stat)
- func (t *Text) Slice(start, end int) string
- func (t *Text) Stat() Stat
- func (t *Text) String() string
- func (t *Text) Undo(sel *Selections) bool
Constants ¶
This section is empty.
Variables ¶
var ErrModifiedSince = errors.New("buffer: el archivo ha cambiado en el disco desde que se abrió")
ErrModifiedSince se devuelve al guardar cuando el archivo ha cambiado en el disco desde que se leyó.
Es el equivalente del FILE_MODIFIED_SINCE de VS Code, y existe por la misma razón: la regla de no recargar nunca un documento con cambios sin guardar —ver Reload— deja abierta la posibilidad de que las dos versiones hayan divergido, y el único momento honesto para decirlo es cuando se va a escribir encima. Guardar sin mirar convertiría la regla que protege el trabajo propio en una que destruye el ajeno.
var NativeEOL = nativeEOL()
NativeEOL es el final de línea que se usa en los archivos nuevos.
Functions ¶
func Transform ¶
Transform traslada una posición a través de un conjunto de ediciones.
Las ediciones deben venir ordenadas y sin solaparse, que es como las produce una transacción. Es la operación de la que dependen los cursores, la selección y cualquier marca que tenga que sobrevivir a lo que se escriba por encima.
Types ¶
type Bias ¶
type Bias uint8
Bias decide hacia dónde se mueve una posición cuando la edición ocurre justo donde está.
No hay una respuesta universal y por eso es un parámetro: el cursor de quien escribe quiere quedarse detrás de lo que acaba de teclear, y el principio de una marca de git quiere quedarse delante de lo que se inserta encima para no tragárselo.
type EOL ¶
type EOL uint8
EOL es el final de línea con el que se escribe el archivo.
type Edit ¶
Edit es una sustitución: quita el texto entre Start y End y pone Text en su lugar.
Las tres operaciones de un editor son casos de esta: insertar es un rango vacío con texto, borrar es un rango con texto vacío, y reemplazar es las dos cosas. Tener una sola forma es lo que permite que deshacer sea la misma operación al revés en lugar de tres caminos distintos que se desincronizan.
type Encoding ¶
type Encoding uint8
Encoding es la codificación con la que estaba escrito el archivo.
FlowCode trabaja por dentro en UTF-8 y punto. Lo que hace esta lista es recordar de dónde venía el archivo para devolverlo igual: convertir a UTF-8 sin avisar un archivo que el resto del equipo tiene en UTF-16 produce un diff de miles de líneas por haber tocado una.
type File ¶
type File struct {
// contains filtered or unexported fields
}
File es un archivo de texto cargado en memoria con su índice de líneas.
Es seguro leerlo desde el hilo de dibujo mientras el indexado avanza en otra goroutine: las consultas nunca se bloquean ni esperan.
func Open ¶
Open carga un archivo y arranca su indexado.
onIndexed se invoca desde otra goroutine cuando el índice completo está listo. Sirve para que la interfaz se repinte con el número real de líneas; puede ser nil.
Los archivos grandes se proyectan en memoria en lugar de copiarse: abrir un gigabyte deja de costar casi un segundo de lectura y pasa a ser inmediato, porque el sistema solo trae del disco las páginas que se llegan a mirar.
func (*File) Close ¶
Close libera la proyección en memoria del archivo.
Tras llamarlo, cualquier acceso al contenido es un error grave: el sistema ya no tiene esas direcciones mapeadas y leerlas aborta el proceso. Solo debe cerrarse cuando nada vaya a volver a dibujarlo.
func (*File) IndexDuration ¶
IndexDuration devuelve lo que tardó el índice completo, o cero si aún no ha terminado.
func (*File) Indexing ¶
Indexing indica si el índice completo todavía se está construyendo. Mientras sea cierto, LineCount devuelve una cota inferior que irá creciendo.
func (*File) Len ¶
Len devuelve el tamaño en bytes. Es Size con el nombre que espera quien lo trata como un documento cualquiera.
func (*File) Line ¶
Line devuelve el contenido de una línea, sin el salto final.
El resultado comparte memoria con el archivo: es una vista, no una copia, y no debe modificarse. Devolver copias haría que desplazarse por un archivo grande generase basura sin parar.
func (*File) LineOf ¶
LineOf devuelve la línea que contiene un desplazamiento y dónde empieza esa línea.
Es una búsqueda binaria sobre el índice, no un recorrido: en un archivo de treinta millones de líneas la diferencia entre las dos cosas es la que hay entre responder al instante y bloquear el fotograma.
type History ¶
type History struct {
// contains filtered or unexported fields
}
History es la pila de deshacer y rehacer.
func (*History) Break ¶
func (h *History) Break()
Break corta la agrupación: lo siguiente que se escriba empieza una operación de deshacer nueva.
Lo llama el editor cuando el cursor se mueve, se guarda el archivo o cambia la selección. Sin eso, escribir aquí, mover el cursor y escribir allá se deshacen de golpe, que no es lo que nadie espera.
type Rope ¶
type Rope struct {
// contains filtered or unexported fields
}
Rope es el texto editable de FlowCode: un árbol equilibrado de trozos de bytes con el número de saltos de línea cacheado en cada nodo.
Por qué un árbol y no un slice ¶
Un editor hace dos cosas todo el rato: insertar o borrar en medio, y traducir entre desplazamiento de byte y (línea, columna). Sobre un []byte plano lo primero mueve el archivo entero en cada tecla y lo segundo obliga a recorrerlo contando saltos. Con el recuento de bytes y de líneas cacheado por nodo, las dos operaciones bajan por el árbol y cuestan O(log n).
Qué garantiza y qué no ¶
El árbol es uniforme en profundidad: todas las hojas están al mismo nivel, porque tanto la división como la fusión ocurren de abajo arriba. No es un B-tree estricto: tras borrar se permite que un nodo quede por debajo de la mitad, y en vez de pedir prestado a un hermano se fusionan los vecinos que quepan juntos. Es bastante más simple y el resultado práctico es el mismo, porque la fusión codiciosa deja los nodos razonablemente llenos.
No es seguro para uso concurrente. Pertenece al hilo que edita.
func (*Rope) LineCount ¶
LineCount devuelve el número de líneas.
Un texto vacío tiene una línea, y uno terminado en salto de línea no tiene una línea vacía detrás. Es la convención de todos los editores: un archivo bien formado termina en salto y nadie espera ver una línea de más.
func (*Rope) LineEnd ¶
LineEnd devuelve el desplazamiento donde termina una línea, sin contar su salto.
func (*Rope) LineOf ¶
LineOf devuelve la línea que contiene un desplazamiento y el desplazamiento donde empieza esa línea.
Devuelve las dos cosas porque quien pregunta por una casi siempre necesita la otra: la columna es la diferencia entre el desplazamiento y el principio de su línea. Son dos descensos del árbol —contar saltos hasta el desplazamiento, y localizar el último— en lugar de uno que lleve la cuenta de ambas cosas a la vez; ese sería más rápido y bastante más fácil de escribir mal, y dos descensos O(log n) siguen siendo O(log n).
func (*Rope) Offset ¶
Offset traduce (línea, columna en bytes) a un desplazamiento absoluto, ajustando a los límites reales del texto.
type Selection ¶
type Selection struct {
Anchor, Head int
// Goal recuerda la columna a la que aspira el cursor al subir y bajar.
//
// Sin ella, bajar desde el final de una línea larga a una corta y volver a
// subir dejaría el cursor a media línea: la columna se habría perdido al
// pasar por la corta. Se mide en bytes desde el principio de la línea, como
// todo lo demás.
Goal int
}
Selection es un rango de texto con dirección: Anchor es donde empezó y Head donde está el cursor.
La cabeza puede quedar antes que el ancla si se seleccionó hacia atrás, y esa distinción importa: al extender con las flechas se mueve la cabeza, nunca el ancla. Un cursor sin selección es una selección con los dos extremos juntos, y no un caso aparte: tratarlos igual elimina la mitad de las ramas de cualquier operación de edición.
type Selections ¶
type Selections struct {
// contains filtered or unexported fields
}
Selections es el conjunto ordenado de cursores del editor.
Se mantiene siempre ordenado y sin solapamientos. Esas dos propiedades no son cosmética: las ediciones de una transacción tienen que ir ordenadas y sin pisarse, y si el conjunto ya lo está, generar la transacción de multi-cursor es recorrerlo.
func NewSelections ¶
func NewSelections() *Selections
NewSelections crea un conjunto con un único cursor al principio del texto.
func (*Selections) Add ¶
func (s *Selections) Add(sel Selection)
Add añade un cursor y lo convierte en el principal.
func (*Selections) All ¶
func (s *Selections) All() []Selection
All devuelve las selecciones en orden. No debe modificarse.
func (*Selections) Clone ¶
func (s *Selections) Clone() []Selection
Clone copia el conjunto, para guardarlo en el historial.
func (*Selections) Primary ¶
func (s *Selections) Primary() Selection
Primary devuelve el cursor principal.
func (*Selections) Replace ¶
func (s *Selections) Replace(list []Selection)
Replace sustituye el conjunto entero.
func (*Selections) Transform ¶
func (s *Selections) Transform(edits []Edit)
Transform traslada todas las selecciones a través de unas ediciones.
func (*Selections) Update ¶
func (s *Selections) Update(f func(Selection) Selection)
Update transforma cada selección con una función y vuelve a ordenar y fusionar.
Es la única forma de mover los cursores, y por eso la fusión ocurre sola: dos cursores que se encuentran al desplazarse tienen que convertirse en uno, o a partir de ahí todo lo que se escriba saldría duplicado.
type Stat ¶
Stat es la huella del archivo en el disco: cuándo se modificó y cuánto ocupa.
Es el equivalente del etag de VS Code, que guarda lo mismo —mtime, tamaño— y lo usa para lo mismo: saber si el archivo que hay ahora en el disco sigue siendo el que se leyó. Con dos campos basta; comparar el contenido entero obligaría a leer el archivo antes de cada guardado.
type Text ¶
type Text struct {
// contains filtered or unexported fields
}
Text es un documento editable: el texto, su historial y lo que hace falta para volver a escribirlo en el disco tal y como estaba.
El texto vive siempre con saltos de línea Unix ¶
Un archivo con finales de línea de Windows se convierte al cargarlo y se vuelve a convertir al guardarlo. La alternativa —arrastrar el retorno de carro por dentro— obliga a que cada operación sobre columnas, cada búsqueda y cada cálculo de longitud recuerde que a veces hay un byte invisible al final de la línea, y basta que uno se olvide para tener un error que solo aparece en las máquinas de otros.
func (*Text) Apply ¶
func (t *Text) Apply(edits []Edit, sel *Selections) error
Apply aplica una transacción, la registra en el historial y traslada los cursores.
Es el único camino por el que cambia el texto. Que sea único es lo que garantiza que no pueda haber una edición sin su entrada de deshacer, ni un cursor apuntando a un sitio que ya no existe.
func (*Text) LineOf ¶
LineOf devuelve la línea que contiene un desplazamiento y dónde empieza esa línea.
func (*Text) LineStart ¶
LineStart y LineEnd delegan en el rope; existen para que quien tiene un Text no necesite pedirle además su estructura interna.
func (*Text) Reload ¶
func (t *Text) Reload(sel *Selections) error
Reload vuelve a leer el archivo del disco, descartando lo que hubiera en memoria.
Deshacer sigue funcionando después: la recarga entra como una transacción más, así que recargar sin querer sobre un documento con cambios se arregla con Ctrl+Z en vez de perder el trabajo. Es lo que hace VS Code, que aplica el contenido nuevo al modelo de texto en lugar de reemplazarlo.
func (*Text) Save ¶
Save escribe el documento en su ruta, salvo que el archivo haya cambiado en el disco desde que se leyó.
func (*Text) SaveOverwrite ¶
SaveOverwrite escribe el documento pase lo que pase, descartando lo que haya en el disco. Es la salida del conflicto cuando quien edita decide que su versión es la buena.
func (*Text) SetStat ¶
SetStat fija la huella conocida del archivo.
Lo usa la restauración de una copia de seguridad: el documento vuelve con el contenido que no llegó a guardarse, pero la huella que se arrastra es la que tenía el archivo cuando se hizo la copia, no la de ahora. Así, si el archivo cambió en el disco mientras el editor estaba cerrado, el conflicto salta al guardar en vez de sobrescribir en silencio. Es exactamente lo que hace VS Code al reconstruir un modelo desde su backup.
func (*Text) Stat ¶
Stat devuelve la huella del archivo tal y como estaba la última vez que se leyó o se escribió.
func (*Text) Undo ¶
func (t *Text) Undo(sel *Selections) bool
Undo deshace el último grupo de ediciones y devuelve si había algo que deshacer.