Saltar a contenido

Configuración

Los ajustes de Textchum siguen un principio: la interfaz gráfica es la forma cómoda de cambiarlos, y un archivo JSON plano es la salida de emergencia siempre disponible. Hay exactamente un almacén — el archivo — y la ventana de Ajustes lo lee y lo escribe; nada vive solo dentro de la aplicación.

La ventana de Ajustes

Textchum → Settings… (⌘,) edita los ajustes reconocidos:

  • Apariencia — seguir al sistema (cambiando en vivo cuando macOS lo hace) o forzar claro u oscuro.
  • Tema — la paleta de sintaxis; véase Temas más abajo.
  • Iconos de archivo — un paquete de iconos de VS Code para el árbol de archivos; ver Iconos de archivo.
  • Abrir archivos en — pestañas de la ventana actual (el valor por defecto) o ventanas separadas. Con ventanas separadas, el navegador de cada ventana lista solo los documentos de su propio grupo de pestañas.
  • Tipografía — cualquier familia de ancho fijo instalada en el sistema, o la tipografía monoespaciada de la plataforma.
  • Tamaño de letra — de 6 a 72 puntos.
  • Ancho de tabulación — de 1 a 16 columnas.
  • Close brackets and quotes as they are typed — apagado por defecto; editor.auto_close_pairs en el archivo. Véase el recorrido.
  • Mostrar números de línea — el margen, también conmutables por sesión con View → Toggle Line Numbers (⇧⌘L).

Cada cambio se aplica de inmediato a las ventanas abiertas del editor y se escribe a disco en el mismo momento. No hay botón de Aplicar o Guardar que olvidar.

El archivo

Los ajustes viven en:

~/Library/Application Support/Textchum/config.json

Un archivo editado a mano podría verse así:

{
  "appearance": "dark",
  "editor": {
    "font_family": "JetBrains Mono",
    "font_size": 13,
    "tab_width": 4,
    "hover": false,
    "hover_modifier": "option"
  }
}

appearance acepta "system", "light" u "dark"; si se omite (el valor por defecto), se sigue al sistema. editor.hover apaga el globo de documentación al reposar el ratón (true, el valor por defecto, lo mantiene encendido); editor.hover_modifier — "shift", "control", "option" u "command" — hace que además espere esa tecla, y omitirlo significa solo el ratón. editor.new_files_in coloca los documentos nuevos en una "tab" del grupo de la ventana frontal (el valor por defecto) o en una "window" propia. editor.mark_occurrences (true por defecto) marca los demás lugares donde aparece la palabra seleccionada; editor.occurrences_case_sensitive y editor.occurrences_whole_word deciden qué cuenta como uno, ambos true por defecto. editor.rainbow_brackets (false por defecto) colorea los pares de corchetes por profundidad; véase resaltado. editor.inlay_hints (true por defecto) muestra los tipos que dedujo un servidor de lenguaje, atenuados, tras la línea a la que pertenecen; véase servidores de lenguaje. editor.context_lines (true por defecto) fija la primera línea de cada construcción envolvente en lo alto de la vista mientras un cuerpo largo se desplaza; false apaga las líneas fijadas. editor.git_marks elige con qué compara la barra de cambios del margen: "head" (el valor por defecto) con el último commit, "branch" con el commit del que nació la rama. editor.merge_base_branches es la lista de nombres de rama que se prueban, en orden, cuando git no nombra una rama por defecto — ["main", "master", "trunk", "develop"] si no se establece. Ambas claves funcionan también por proyecto, como anulaciones editor de una entrada del workspace.

editor.pairs da a un lenguaje su propia tabla de pares, para Close brackets and quotes as they are typed y para envolver una selección: para un lenguaje que la regla incorporada no conoce, o en el que se equivoca con una comilla. Las claves son nombres de lenguaje tal como los muestra la barra de estado; cada entrada es la mitad de apertura y luego la de cierre:

{
  "editor": {
    "auto_close_pairs": true,
    "pairs": {
      "nix": ["()", "[]", "{}", "\"\""],
      "rust": ["()", "[]", "{}", "\"\"", "<>"]
    }
  }
}

Una tabla es la respuesta completa para su lenguaje — nada de la regla incorporada se le añade, así que una comilla que falte sigue siendo una pulsación normal. Un par cuyas dos mitades son el mismo carácter es una comilla, que nunca se abre justo después de una palabra ni delante de otra igual, y ningún par se abre dentro de una palabra, con tabla o sin ella. Todavía no: la ventana de Ajustes no edita la tabla, y el texto sin formato no puede tener una.

Todo es opcional: un archivo, una sección o una clave ausentes significan simplemente el valor por defecto. Las escrituras son atómicas (archivo temporal más renombrado), como toda escritura que hace Textchum.

Dos garantías hacen segura la edición a mano:

  • Las claves desconocidas sobreviven. La ventana de ajustes reescribe solo las claves que le pertenecen. Cualquier otra cosa en el archivo — anotaciones propias, claves de una versión más nueva — se conserva literalmente en cada guardado.
  • Los archivos rotos nunca se pisotean. Si el archivo no se puede analizar, Textchum arranca con los ajustes por defecto, lo avisa una vez al iniciarse y deja el archivo exactamente como estaba para que pueda arreglarse en cualquier editor — incluido el propio Textchum. Si se cambia un ajuste desde la interfaz mientras el archivo está roto, el original inanalizable se copia primero a config.json.bak y solo entonces se reemplaza.

Los valores fuera de rango o con tipo incorrecto no cuentan como rotura: un font_size de 4000 se recorta al rango válido, un font_family de 42 se ignora y el resto del archivo funciona con normalidad.

En otro sitio, por una ejecución

--data-dir <ruta> guarda todo lo que Textchum posee bajo un único directorio durante esa ejecución —la configuración, los temas, los paquetes de iconos, la sesión y el registro del servidor de lenguaje— en vez de en los sitios de siempre:

Textchum --data-dir ~/perfil-de-pruebas

Es un perfil entero hecho para la ocasión y desechado después, sin que el de verdad se abra nunca; make playground lo usa, y también cualquier otra cosa que no deba tocar tus ajustes. En Linux una ejecución con perfil propio es su propio proceso, ya que entregar los archivos a una instancia ya en marcha los abriría en el perfil de esa instancia.

--config <ruta> es la versión estrecha: apunta a un archivo de configuración, y la sesión lo acompaña.

Temas

El selector Theme de la pestaña General elige la paleta de sintaxis. Se incluyen siete de serie: Textchum (el predeterminado), Textchum High Contrast, Graphite (uno apagado casi monocromo) y los clásicos — Molokai, Solarized, Dracula y Gruvbox. Cada tema lleva una paleta clara y una oscura en un mismo archivo, de modo que un tema sirve para ambos modos de apariencia (los clásicos nacidos oscuros emparejan su paleta canónica con una clara ajustada en contraste; Solarized y Gruvbox usan sus paletas claras genuinas).

Los temas propios son archivos JSON en:

~/Library/Application Support/Textchum/themes/

seleccionados por nombre de archivo (sin .json); un archivo con el nombre de un tema incorporado lo reemplaza. Textchum → Open Themes Folder abre (y crea) este directorio. La forma más rápida de empezar uno es generar un arranque completo — cada nombre de captura con estilo, relleno con la paleta por defecto — y solo cambiar colores:

Textchum --emit-theme ~/Library/Application\ Support/Textchum/themes/Mio.json

Las entradas asocian nombres de captura de tree-sitter a estilos:

{
  "name": "Mio",
  "styles": {
    "keyword": {"light": "#AD3DA4", "dark": "#FC5FA3", "bold": true},
    "comment": {"light": "#707F8C", "dark": "#7F8C98", "italic": true}
  }
}

Los colores son #RRGGBB o #RRGGBBAA. Todo lo omitido — un color, un indicador, una captura entera — conserva el valor de la paleta por defecto, así que un tema solo necesita decir lo que cambia. Las reglas de salida de emergencia son las de la configuración: un tema que no se puede analizar cae al predeterminado con un único aviso y nunca se sobrescribe, y las claves desconocidas sobreviven. Los archivos de tema se leen al arrancar y al cambiar la selección.

Importar uno de otro editor

Textchum → Importar tema trae los colores de VS Code o TextMate. Elige un archivo de tema, o una carpeta con varios — un directorio de extensión de VS Code (su package.json dice qué aporta) o un bundle de TextMate (sus temas están en Themes/). Se importa todo lo que haya, y el primero queda puesto. Lo importado, y lo que no pasó, se dice en el área de notificaciones a la derecha de la barra de estado; un clic allí lo muestra entero.

Los dos editores describen el color por ámbito de TextMate, así que importar es traducir ámbitos a los nombres de captura de Textchum: entity.name.function pasa a ser function, y keyword.control.loop pasa a ser repeat. Los ámbitos se detienen donde las capturas siguen —ningún tema colorea if distinto de while— así que una captura que el origen nunca nombró toma el color de aquella de la que es un caso particular, en cualquier dirección: un tema que dice keyword colorea todas las clases de palabra clave, y uno que sólo dice constant.numeric colorea toda la familia de constantes.

Dos cosas conviene saber antes de que los colores parezcan mal:

  • Un tema llena una apariencia. Los dos editores escriben un tema para fondo claro o para fondo oscuro; los de Textchum llevan ambos. La importación llena el lado que el origen declara y deja el otro con la paleta por omisión, y dice cuál llenó. Importar un tema oscuro con el editor en apariencia clara no cambia nada visible.
  • Los ámbitos sin destino se nombran. Todo lo que el origen coloreó y a lo que ninguna captura responde se lista al terminar. Esos colores quedan sin usar.

Lo que sale es un archivo de tema corriente en la carpeta de temas, editable como cualquier otro.

Iconos de archivo

El árbol de archivos dibuja un icono por fila. Sin un paquete es el que ofrezca el escritorio para el tipo del archivo, que distingue Python de Markdown y no llega mucho más lejos — y nunca ha oído hablar de un archivo llamado Dockerfile.

Ajustes → General → Iconos de archivo acepta un paquete de iconos de VS Code. Los paquetes ya vistos están en la lista, separados entre los importados aquí y los abiertos donde están; System icons es la vuelta atrás.

Import… copia el paquete a la carpeta propia de Textchum — ~/Library/Application Support/Textchum/icons/ en macOS, ~/.local/share/textchum/icons/ en Linux— así que mover o borrar el original no se lleva los iconos. Open… apunta a un paquete donde está y lo recuerda, que es lo adecuado para uno que mantienes tú. Ambos aceptan el archivo JSON del tema de iconos o la carpeta de extensión que lo contiene (su package.json dice cuál es). Un paquete puesto a mano en la carpeta de Textchum también aparece en la lista — una carpeta de extensión, o el archivo JSON del tema por sí solo — y el icono del archivo en la barra de título sigue al paquete, como el árbol. Delete borra un paquete importado; uno abierto desde otro sitio es de quien lo puso ahí, así que solo se puede quitar de la lista.

La elección es una ruta en config.json, y los paquetes abiertos desde otro sitio se recuerdan a su lado:

{
  "icon_pack": "~/packs/material-icon-theme/dist/material-icons.json",
  "icon_packs": ["~/packs/material-icon-theme/dist/material-icons.json"]
}

Un paquete cuya carpeta ya no está desaparece de la lista en vez de quedarse ahí para fallar al elegirlo. Un paquete que no se puede leer se avisa una vez y el árbol conserva los iconos del sistema.

La búsqueda sigue la de VS Code, de lo más específico a lo menos:

  1. El nombre entero del archivo (Dockerfile, cargo.toml), en minúsculas.
  2. La extensión más larga que coincida: component.test.ts prueba test.ts antes que ts.
  3. El lenguaje que Textchum decidió que es el archivo — que es también como llega al icono un lenguaje fijado a mano en Propiedades del archivo.
  4. El valor por omisión del propio paquete.

La sección light de un paquete sustituye cualquiera de esos sobre fondo claro, una búsqueda a la vez, así que un paquete que solo redibuja unos pocos conserva el resto.

Quedan fuera dos cosas. Los iconos de carpeta: el árbol dibuja los suyos. Los iconos por tipografía —las definiciones fontCharacter que usan Seti y sus descendientes— necesitan la tipografía instalada y un texto donde va una imagen; un paquete que no tenga otra cosa se rechaza dando esa razón, en vez de cargarse para no dibujar nada.

Proyectos

La pestaña Projects decide dónde empieza y termina un proyecto — el límite por el que agrupa el navegador y con el que el pool de servidores de lenguaje identifica sus instancias. Ambos interruptores existen dos veces: como valor por defecto para todos los proyectos y por raíz de proyecto. Una fila añadida con el campo de ruta (que completa nombres de directorio al escribir y lleva un botón Browse…) anula los valores por defecto solo para esa raíz.

  • Manifest projects — normalmente gana el repositorio más externo: abrir un archivo en cualquier punto dentro de un repositorio convierte al repositorio en el proyecto, por muchos Cargo.toml o pyproject.toml que haya en medio. Activarlo vuelve a dividir una raíz por manifiestos de lenguaje, de modo que los módulos anidados son proyectos propios.
  • Recursive config — hace que las entradas de servidor de lenguaje de una raíz se apliquen a los proyectos anidados dentro de ella, con prioridad para el ancestro más cercano, y que el servidor de la raíz se ocupe de ellos: un proyecto anidado sin entrada propia lo atiende la instancia que corre en la raíz, que ve el árbol entero. Útil en monorepos: una configuración, y un servidor, arriba; muchos proyectos debajo. La pestaña Language Servers muestra el mismo interruptor junto a cada entrada de proyecto, como Nested projects too.
  • Separate nested servers — bajo recursive config, da a cada proyecto anidado un servidor propio: la línea de comando de la raíz, iniciada en el proyecto anidado, con {project} nombrándolo a él. Para miembros que llevan cada uno su entorno — un .venv por paquete — donde un servidor arriba no vería ninguno. A server each, en la pestaña Language Servers.
  • Ctags fallback — responde Ir a la Definición desde un índice de Universal Ctags cuando no hay servidor de lenguaje disponible; véase servidores de lenguaje.

En el archivo, esto vive en una sección workspace:

{
  "workspace": {
    "manifest_projects": false,
    "recursive_config": false,
    "separate_nested_servers": false,
    "ctags_fallback": false,
    "projects": {
      "/Users/you/code/monorepo": {
        "manifest_projects": true,
        "recursive_config": true
      }
    }
  }
}

Preprocesadores de guardado

Los formateadores y correctores pueden ejecutarse automáticamente antes de cada guardado, por lenguaje — para todos los proyectos o para una raíz concreta, exactamente como los servidores de lenguaje. Cada entrada es una cadena: un comando por línea, en orden, donde cada comando lee el documento por la entrada estándar y escribe el documento completo por la salida estándar (la convención - que siguen casi todos los formateadores). Si un eslabón falla — salida distinta de cero, salida vacía, o más de diez segundos colgado — no se aplica nada, se muestra el error (con el stderr de la herramienta) y el guardado pregunta si continuar sin procesar. Esa pregunta se puede responder de una vez: When a chain fails, save anyway (Ajustes ▸ Preprocessors; "on_failure": "save" en la sección de abajo) escribe el documento tal como está y lo dice un momento en la barra de estado, con las palabras de la herramienta en el registro — para quien siempre responde que sí.

{
  "preprocessors": {
    "defaults": {
      "python": ["ruff check --fix -", "black -"],
      "go": ["gofmt"]
    },
    "projects": {
      "/work/site": { "javascript": ["prettier --stdin-filepath {filename}"] }
    }
  }
}

{path} y {filename} en cualquier parte de un comando se expanden a la ruta absoluta del documento y a su nombre — para herramientas que leen stdin pero deducen su comportamiento del nombre, como el --stdin-filepath de Prettier. Un documento sin título ofrece Untitled más la extensión de su lenguaje.

{edition} se expande a la edición de Rust del crate al que pertenece el documento: la del Cargo.toml más cercano, o la de su workspace cuando el paquete la hereda. rustfmt, leyendo de la entrada estándar, nunca ve el manifiesto y formatea como 2015 si no se le dice otra cosa, así que rustfmt --edition {edition} es como una cadena formatea igual que cargo fmt. Un archivo fuera de todo crate recibe 2021.

Una entrada de proyecto reemplaza la cadena por defecto para ese lenguaje, nunca se añade a ella. La ventana de Ajustes edita esta misma sección bajo Servidores de lenguaje, y Edición ▸ Ejecutar preprocesadores (⌃⌥⌘F, nombre de acción runPreprocessors) ejecuta la cadena a demanda sin guardar — formatear con tus herramientas en vez del formateador del servidor. El resultado llega como una sola edición, así que ⌘Z lo deshace.

El eslabón @format no es un programa: pide al servidor de lenguaje del documento que formatee, y el guardado espera hasta dos segundos la respuesta. Es como un formateador que necesita la configuración propia del proyecto la obtiene — rust-analyzer formatea con la edición del crate, gopls con sus opciones configuradas — y puede ir en cualquier lugar de una cadena, antes o después de los comandos. Un servidor que no puede formatear (ninguno en marcha, ninguno que formatee, ninguno que respondiera a tiempo) no hace fallar la cadena: el texto sigue como está y el área de notificaciones dice por qué.

Herramientas conocidas

Nadie debería tener que averiguar que black quiere un guion al final y prettier un nombre de archivo. El menú Known tools — junto a cada cadena en Settings ▸ Preprocessors, y en el formulario que añade una — lista las herramientas cuya línea de comando el editor conoce para el lenguaje en cuestión, marca cuáles están instaladas, y escribe la línea al elegir una. Una herramienta que no está instalada se ofrece igual, con el comando que la instala. Known servers hace lo mismo para el comando del servidor en Language Servers.

Herramienta Lenguajes La línea que escribe
rustfmt rust rustfmt --edition {edition}
gofmt go gofmt
goimports go goimports
gofumpt go gofumpt
golines go golines
ruff format python ruff format --stdin-filename {path} -
ruff check --fix python ruff check --fix --exit-zero --no-cache --stdin-filename {path} -
ruff (imports) python ruff check --select I --fix --exit-zero --no-cache --stdin-filename {path} -
black python black --quiet --stdin-filename {path} -
isort python isort --stdout --filename {path} -
autopep8 python autopep8 -
yapf python yapf --quiet
autoflake python autoflake --remove-all-unused-imports --stdin-display-name {path} -
usort python usort format -
prettier javascript, typescript, tsx, css, html, json, yaml, markdown prettier --stdin-filepath {path}
prettierd javascript, typescript, tsx, css, html, json, yaml, markdown prettierd {path}
biome javascript, typescript, tsx, json, css biome format --stdin-file-path {path}
dprint javascript, typescript, tsx, json, markdown, toml dprint fmt --stdin {path}
eslint_d javascript, typescript, tsx eslint_d --fix-to-stdout --stdin --stdin-filename {path}
clang-format c, cpp, java, csharp clang-format --assume-filename={path}
astyle c, cpp astyle
shfmt bash shfmt -filename {path}
beautysh bash beautysh -
stylua lua stylua --search-parent-directories --stdin-filepath {path} -
zig fmt zig zig fmt --stdin
terraform fmt hcl terraform fmt -
tofu fmt hcl tofu fmt -
packer fmt hcl packer fmt -
nomad fmt hcl nomad fmt -
jq json jq .
fixjson json fixjson
taplo toml taplo format -
yamlfmt yaml yamlfmt -
mdformat markdown mdformat -
xmllint xml xmllint --format -
sql-formatter sql sql-formatter
sqlfmt sql sqlfmt -
pg_format sql pg_format
swiftformat swift swiftformat --stdinpath {path}
rubocop ruby rubocop --auto-correct-all --fail-level fatal --stderr --force-exclusion --stdin {path}
standardrb ruby standardrb --fix --fail-level fatal --stderr --stdin {path}
nixfmt nix nixfmt
alejandra nix alejandra --quiet -
nixpkgs-fmt nix nixpkgs-fmt
ormolu haskell ormolu --stdin-input-file {path}
fourmolu haskell fourmolu --stdin-input-file {path}
stylish-haskell haskell stylish-haskell
ocamlformat ocaml ocamlformat --enable-outside-detected-project --name {path} -
ocp-indent ocaml ocp-indent
mix format elixir mix format --stdin-filename {path} -
scalafmt scala scalafmt --stdin
google-java-format java google-java-format -
cmake-format cmake cmake-format -
gersemi cmake gersemi -

Una línea es texto corriente una vez escrita: se le pueden añadir opciones, reordenar la cadena o borrarla, como con cualquier otra. La tabla se prueba contra las herramientas que tenga la máquina que corre las pruebas.

Todavía no: una herramienta fuera de esta tabla hay que escribirla a mano, y las que solo reescriben archivos en el lugar, en vez de trabajar sobre la entrada estándar, no están listadas.

Perfiles de lenguaje

Un lenguaje rara vez es una sola herramienta, así que Settings ▸ Presets ofrece los habituales como un conjunto: el servidor, la configuración con la que debe correr y la cadena de preprocesadores de guardado.

Perfil Servidor Configuración Antes de cada guardado
Rust rust-analyzer comprueba con clippy rustfmt --edition {edition}
Python pyright, con ruff al lado — ruff check --fix …, luego ruff format
Go gopls staticcheck, su coloreado de nombres, y sus tipos inferidos y nombres de parámetro como pistas goimports
TypeScript y JavaScript typescript-language-server — prettier --stdin-filepath {path}
C y C++ clangd — clang-format --assume-filename={path}

Apply escribe entradas corrientes — lsp.defaults, lsp.settings y preprocessors.defaults — y reinicia los servidores. Nada recuerda que se usó un perfil: lo que escribió es tuyo para editar en Language Servers y Preprocessors, y el botón dice Applied mientras la configuración siga diciendo todo lo que el perfil escribiría.

Cada perfil lista los programas que ejecuta y si cada uno se encontró en PATH; el que falta muestra el comando que lo instala. El editor no instala nada por su cuenta.

Todavía no: un perfil se aplica a todos los proyectos (las entradas que escribe se pueden mover a un proyecto a mano). Y el perfil de TypeScript no configura eslint como segundo servidor, porque el servidor de eslint necesita la configuración propia del proyecto para decir algo.

Corrección ortográfica

La prosa usa el corrector del sistema — los mismos diccionarios que comparte toda app del Mac — acotado a donde la prosa vive de verdad: los comentarios en el código, y el documento entero en Markdown, mensajes de commit de git y texto plano. Los identificadores y las cadenas literales nunca se marcan. Las faltas llevan un tinte púrpura, distinto del rojo/naranja/azul de los diagnósticos.

Elige el idioma en Ajustes ▸ General ▸ «Spell check prose» — Apagado (el valor por defecto), automático por contenido, o un diccionario concreto — o pon editor.spell a mano: "auto" o un identificador como "es" o "en_US". Los diccionarios disponibles son los habilitados en Ajustes del Sistema ▸ Teclado ▸ Entrada de texto.

{ "editor": { "spell": "auto" } }

Pueden aplicarse varios diccionarios a la vez: nómbralos separados por comas. Una palabra que cualquiera de ellos conozca está bien escrita, que es lo que necesita un texto que cambia de idioma a mitad de párrafo:

{ "editor": { "spell": "en_US, es_ES" } }

editor.spell_words es tu propia lista: nombres de proyecto, siglas y todo aquello que ningún diccionario trae. Al hacer clic derecho sobre una palabra marcada aparecen las sugerencias, Añadir al diccionario, que escribe la palabra aquí, e Ignorar, que la acepta hasta cerrar el editor. La lista también se edita en los ajustes.

{ "editor": { "spell_words": ["SBX", "Textchum"] } }

En Linux los mismos ajustes usan hunspell: instala hunspell más un paquete de diccionario (hunspell-es, hunspell-en-us, …) y las marcas aparecen; "auto" sigue a $LANG, y los diccionarios que hunspell encuentra se listan junto al campo en las preferencias.

Autoguardado

Desactivado por defecto. editor.autosave son segundos; el reloj se reinicia con cada pulsación, así que el guardado ocurre cuando dejas de escribir y no en mitad de una frase.

{ "editor": { "autosave": 30 } }

Dos cosas que a propósito no hace. Nunca guarda un documento sin nombre: no hay dónde ponerlo, e inventarle uno no le corresponde al editor. Y no ejecuta los preprocesadores de guardado: un formateador que te reordena la línea que estás escribiendo no es un favor, así que eso se queda en los guardados explícitos.

Atajos de teclado

Ajustes ▸ Teclado los tiene: un perfil, y cada comando con el atajo al que responde, editable ahí mismo.

Perfiles. La gente llega de otro editor con sus atajos en los dedos, así que los tres por los que se los conoce vienen con la compilación — Visual Studio Code, Sublime Text e IntelliJ IDEA. Un perfil nombra los comandos que mueve y deja el resto en paz, así que elegir uno cambia aquello por lo que ese editor es conocido y nada más. keys_profile guarda la elección; vacío son los atajos propios de Textchum.

Cambiar un atajo sobre un perfil conserva el perfil: el cambio es una anulación, y Reset changes las descarta todas. Save as profile convierte lo que está en vigor en un perfil propio — la manera de modificar un preajuste, que viene con la compilación. Los perfiles guardados viven en key_profiles, y uno que reutilice un nombre incluido lo reemplaza.

Una tecla responde a una sola orden. Cuando un perfil o una anulación pone una orden en una tecla que otra tiene por defecto, esa otra orden la cede y su campo queda vacío: un perfil que pone new en cmd+n quiere que Ctrl+N abra una pestaña, no una ventana. En Linux, donde cmd y ctrl son la misma tecla, un perfil que nombra las dos para una letra — cmd+g para findNext, ctrl+g para goToLine — deja la tecla a la asignación con cmd, y la otra orden conserva su propio atajo. La versión para macOS todavía no resuelve estos casos: allí una tecla que comparten dos órdenes solo llega a una de ellas.

El archivo escribe las anulaciones como una sección keys: un objeto de nombres de acción a especificaciones modificadores+tecla, aplicado sobre el perfil.

{
  "keys": {
    "openQuickly": "cmd+p",
    "goToBlockEnd": "ctrl+alt+down",
    "findInProject": "cmd+shift+g"
  }
}

Modificadores: cmd, shift, alt, ctrl — cmd es Command en macOS y Ctrl en Linux, así que un perfil significa lo mismo en ambos. Teclas: un carácter, de f1 a f20, o up/down/left/right/return/escape/space/tab/delete. Entre las acciones: new, open, openQuickly, save, saveAs, showLocalHistory, close, undo, redo, find, findAndReplace, findNext, findPrevious, useSelectionForFind, findInProject, searchEverywhere, runCommand, runAtCaret, runAgain, rerunFailedTests, debugAtCaret, toggleBreakpoint, debugContinue, stepOver, stepInto, stepOut, stopDebugging, toggleDebugPanel, stopRun, nextOutputPlace, previousOutputPlace, toggleRunPanel, jumpToDefinition, goToImplementation, goToTypeDefinition, findReferences, showUsages, codeActions, renameSymbol, formatDocument, runPreprocessors, documentOutline, goBack, goForward, blameLine, goToLine, goToBlockStart, goToBlockEnd, expandSelection, shrinkSelection, addNextOccurrence, addCaretAbove, addCaretBelow, toggleNavigator, togglePreview, toggleLineNumbers, toggleHover, showHover, serverStatus, newWithFormat, revealInTree, reopenClosed, goToLastEdit, recentFiles, recentPlaces, nextFinding, previousFinding, togglePathDisplay, redraw, commandPalette, settings — un nombre desconocido se registra junto a la lista completa. Ir al inicio/fin de bloque (⌃⌥↑/⌃⌥↓ por defecto) salta sobre el bloque sintáctico multilínea más interno alrededor del cursor, cortesía del mismo árbol que alimenta el coloreado. El mismo árbol está detrás de Expandir selección (⌥↑, Alt+Arriba en Linux): cada pulsación amplía la selección un paso del código — el nombre, el interior de sus paréntesis o comillas, los paréntesis con él, la expresión, la sentencia, el bloque, hasta el archivo entero — y Reducir selección (⌥↓, Alt+Abajo en Linux) desanda los mismos pasos. Un archivo que ninguna gramática lee crece por palabra, luego por línea, luego al todo. Reducir solo devuelve lo que tomó expandir; una selección hecha con el ratón o las flechas no tiene pasos que deshacer. Y cuando un atajo se escapa de la memoria por completo, la paleta de comandos (⇧⌘P) busca de forma difusa cualquier acción de menú por su nombre y ejecuta la selección.

La ventana de acordes

Un atajo de tres o cuatro teclas hay que recordarlo y además alcanzarlo. La ventana de acordes funciona al revés: mantenga pulsadas dos teclas modificadoras y un pequeño panel lista lo que se puede hacer, con una tecla junto a cada comando. Pulse la tecla y el comando se ejecuta.

Está desactivada hasta que se elige un par en Ajustes ▸ Teclado ▸ Ventana de acordes. El archivo guarda la elección como keys_chord:

{ "keys_chord": "ctrl+alt" }

Los pares son ctrl+alt, alt+cmd, ctrl+cmd, ctrl+shift y alt+shift; cualquier otra cosa deja la ventana desactivada. Aquí cmd es la tecla misma — Comando en macOS, Súper en Linux — porque un par son dos teclas pulsadas, no un atajo.

La ventana se abre cuando el par lleva un cuarto de segundo pulsado sin nada más, de modo que un atajo que usa esos mismos dos modificadores ni se retrasa ni se pierde: su tecla llega antes. Una vez abierta sigue abierta aunque se suelten las teclas. Los comandos están en grupos — Archivo, Código, Ir, Buscar, Ver — marcados con un +; la tecla de un grupo abre su lista, y los comandos más usados (la paleta de comandos, la autoría de la línea, Preferencias) están arriba sin grupo de por medio. Una tecla que no es nada se rechaza y la ventana sigue ahí; Escape o un clic la cierran.

El menú es el mismo en macOS y en Linux, y es fijo: sus teclas todavía no se pueden cambiar, y a un comando que no está en él se llega por su atajo o por la paleta de comandos. En Linux el escritorio puede quedarse con un par — Súper sola suele abrir la vista de actividades, y Alt+Mayús cambia la distribución del teclado en algunas configuraciones —, en cuyo caso la ventana nunca lo recibe; elija otro par.

Recarga en vivo

El archivo se vigila mientras Textchum corre: edita config.json en otro editor y el cambio se aplica en cuanto aterriza — apariencia, tema, tipografías, atajos, tabla de servidores, todo, incluida la ventana de Ajustes si está abierta. Los guardados de la propia app se reconocen y se ignoran, y un archivo que momentáneamente no parsea cae a los valores por defecto sin ser sobrescrito, igual que al arrancar.

Ajustes del editor por proyecto

Una raíz de proyecto puede sobrescribir la tipografía, su tamaño y el ancho de tabulación para todas las ventanas dentro de ella — las filas de la pestaña Proyectos llevan los tres campos (vacío significa «heredar el valor general»), y el archivo lo escribe como un objeto editor en la entrada del workspace:

{
  "workspace": {
    "projects": {
      "/work/legacy": { "editor": { "tab_width": 8, "font_size": 12 } }
    }
  }
}

Añadir un proyecto

La pestaña Proyectos lista las raíces de los documentos abiertos, así que un proyecto se añade eligiéndolo y no escribiendo su ruta. Copiar ajustes de parte de uno ya configurado — sus servidores, comandos de guardado, indicadores y ajustes del editor, todo — que es lo que necesita un segundo servicio con la misma disposición. La misma opción está en la fila de cada proyecto, para copiar sobre uno que ya existe.

Un campo de anulación vacío muestra lo que hereda, así que una casilla en blanco dice qué se aplica en vez de dejarte ir a mirar.

Una raíz configurada cuyo directorio ya no está se marca como missing, y Remove missing olvida todas: nada volverá a coincidir con esas entradas.

Lenguajes que la compilación no conoce

El coloreado de Textchum viene de gramáticas tree-sitter compiladas en el binario. Un lenguaje que no trae se puede nombrar en languages, con la gramática como biblioteca compilada y su consulta de resaltado como archivo:

{
  "languages": {
    "kdl": {
      "grammar": "~/.local/share/textchum/grammars/libtree-sitter-kdl.dylib",
      "highlights": "~/.local/share/textchum/grammars/kdl/highlights.scm",
      "extensions": ["kdl"]
    }
  }
}

aliases, filenames e injections son opcionales, y symbol también: el constructor es tree_sitter_<nombre> salvo que se nombre, con guiones y puntos vueltos guiones bajos. Una gramática hecha para otro tree-sitter se rechaza por su número de ABI en lugar de confiar en ella y romper, y un nombre que la compilación ya conoce queda reemplazado por el configurado: así se arregla una gramática vieja sin esperar una versión nueva.

Textchum ▸ Install Grammar… escribe esa entrada por ti a partir del repositorio de una gramática — un checkout de cualquier gramática tree-sitter, que es con lo que Neovim, Helix y Zed colorean también. Compila el analizador con el compilador de C del sistema en la carpeta grammars del perfil, copia las consultas de resaltado e inyección a su lado, lee el nombre y los tipos de archivo del tree-sitter.json del repositorio y carga el resultado, así que un archivo de ese tipo abierto en una pestaña se colorea antes de que se cierre el diálogo. Las consultas escritas para otro editor se reescriben para este: los nombres de captura para los que los temas de aquí no tienen color se asignan a los que sí (keyword.repeat pasa a ser repeat, markup.bold pasa a ser text.strong), un predicado con patrón de Lua se convierte en la expresión regular que tree-sitter evalúa, y un predicado que el editor no puede evaluar se nombra en el informe en vez de dejarlo coincidir con todo en silencio. Sin compilador de C, el comando dice qué instalar; un repositorio sin src/parser.c necesita antes tree-sitter generate, y lo dice. Todavía no: clonar desde una URL — clónalo y elige la carpeta — y una lista de lo instalado, que por ahora es la propia sección languages.

Para construir una a mano, desde el repositorio de la gramática:

cc -O2 -fPIC -shared -I src -o libtree-sitter-NOMBRE.dylib src/parser.c src/scanner.c

(.so en Linux, y sin src/scanner.c cuando la gramática no lo trae.) Una entrada que no se puede cargar cuesta ese lenguaje y nada más: el editor dice qué pasó y sigue. La sección se vuelve a leer cada vez que el archivo cambia, así que una gramática añadida o arreglada con el editor en marcha llega sin reiniciar, y los documentos ya abiertos se analizan con ella — salvo aquel cuyo lenguaje elegiste a mano, que conserva tu elección.

El idioma de la interfaz

La interfaz habla inglés, español o francés; Interface language en Preferencias elige uno, y System sigue al de la máquina. El cambio se aplica en el siguiente arranque.

Los catálogos son gettext y viven en el núcleo, así que los dos shells y el núcleo dicen lo mismo con las mismas palabras. La fuente es core/textchum-core/i18n/<idioma>.po —el formato que hablan quienes traducen y sus herramientas— y la compilación produce el .mo que lee el editor. Una frase sin traducir se lee como lo que dice en inglés en lugar de como una clave que falta.

Un catálogo propio va en el perfil y se lee en lugar del incluido:

msgfmt -o ~/.config/textchum/translations/es.mo mi-es.po
  • ~/Library/Application Support/Textchum/translations/<idioma>.mo
  • ~/.config/textchum/translations/<idioma>.mo

Un archivo con el nombre de un idioma que la compilación no trae se lee igual, así que un cuarto idioma es un catálogo y no una versión nueva.

Para trabajar las traducciones del repositorio, scripts/i18n.sh extrae las cadenas y las mezcla en cada catálogo como manda gettext: xgettext las encuentra, msgmerge conserva lo traducido y marca como dudoso lo que cambió en inglés, y msgfmt comprueba el resultado. make check ejecuta scripts/i18n.sh --check, así que una cadena nueva sin traducir rompe la compilación en vez de salir en inglés.

Registros de proyecto

Un archivo recuerda cómo está dividido, dónde miraba cada vista, qué está plegado y qué se le dijo que era cuando su nombre no lo dice. Eso son datos del archivo, así que viven con el proyecto en lugar de en config.json: un registro por raíz de proyecto, JSON como todo lo demás.

{
  "version": 1,
  "root": "/work/engine",
  "files": {
    "src/parser.rs": {
      "views": 2,
      "dividers": [0.45],
      "folds": [[12, 48]],
      "language": "rust",
      "places": [{"caret": 812, "scroll": 240.0, "top": 790}]
    }
  }
}

Los registros se guardan en el perfil, junto a la sesión y los temas, así que una ejecución apuntada a un perfil de prueba escribe los suyos. Keep each project's state with the checkout pone el registro en <root>/.tchum, para una disposición que viaje con el clon; la opción es global, porque una respuesta por proyecto tendría que anotarse centralmente para poder encontrarse.

La limpieza corre al arrancar en un hilo propio: olvida los registros de proyectos que ya no están y los que no se escriben desde hace más que la ventana (90 días por defecto; cero los conserva hasta que se borren a mano). Forget records at launch la apaga, y Manage… junto a la carpeta de registros lista lo que hay, de qué trata cada uno y cuándo se escribió, para olvidarlos de a uno o de una pasada.

Aún no está

  • Nada por el momento — apunta la próxima molestia cuando aparezca.