# Consola

> Un registro estilo dev tools con niveles, filtro, copiar y limpiar. Componente de Pyzed para GUIs de escritorio en Python, con ejemplos y API.

Source: https://albakore.github.io/pyzed/es/library/console/

:::tip[En pocas palabras]
Una lista de mensajes de la app (información, avisos, errores) que puedes filtrar y copiar.
:::

`ui.Log` reúne entradas (seguro entre hilos, con un tope de `limit`, líneas repetidas agrupadas) y puede capturar `print` y las trazas de error. `ui.console` las muestra como la consola de las dev tools de un navegador: un filtro, un interruptor por nivel con su cuenta, copiar y limpiar. Los niveles son `debug`, `info`, `event`, `stdout`, `stderr`, `warning` y `error` (`ui.LOG_LEVELS`). Con un `ui.LogSelection` las líneas se pueden seleccionar (clic, Shift+clic, Ctrl+clic) y una línea seleccionada muestra su `detail` (un traceback); `on_context` abre tu menú con el clic derecho y `sources` añade un desplegable para ver las líneas de un origen.

`ui.console` · Categoría: [Código y herramientas](https://albakore.github.io/pyzed/es/library/#código-y-herramientas) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#console)

**Código:**

```python
log = ui.Log()
log.write("info", "Loaded ui/main.pzform")
log.write("event", "button1.click")
log.write("stdout", "Saving 3 files…")
log.write("warning", "settings.json not found, using defaults")
log.write("error", "ValueError: invalid literal for int()")
pz.container(ui.console(log.entries, on_clear=on_clear, on_copy=on_copy, on_query=on_query),
             width=600, height=220)
```

## Cuándo usarlo

- Para un panel de depuración en tu app, o para mostrar lo que hace una tarea en segundo plano.

## Uso

```python
import pyzed as pz
from pyzed import ui

class Debug(pz.App):
    def __init__(self):
        self.log = ui.Log(on_write=lambda: pz.post("log"))  # redraw when a line arrives
        self.log.capture()                                   # also collect print() and stderr
        self.query, self.hidden = "", set()

    def view(self):
        return ui.console(self.log.entries, query=self.query,
                          on_query=lambda q: setattr(self, "query", q),
                          hidden=self.hidden, on_toggle_level=lambda lvl: self.hidden.symmetric_difference_update({lvl}),
                          on_clear=self.log.clear, on_copy=lambda: pz.Command.copy(self.log.text()))
```

## Variantes

:::note[Sobre estas capturas]
Cada captura la graba pyzed mismo, fuera de pantalla a 2x, en One Dark o One Light según el tema de este sitio, y recortada al componente para que se vea a su tamaño real. El puntero pasa por encima, pulsa o escribe en él; un componente que no reacciona al puntero recorre algunos temas. Con movimiento reducido ves el primer fotograma. Abre **Código** bajo una captura para ver el fragmento que la dibuja; los nombres libres como `on_save` son tus propios callbacks.
:::

### Opciones

```python
log = ui.Log()
for level in ui.LOG_LEVELS:
    log.write(level, f"{level} message")
pz.container(ui.console(log.entries, on_clear=on_clear), width=520, height=220)
```

```python
log = ui.Log()
for level in ui.LOG_LEVELS:
    log.write(level, f"{level} message")
pz.container(ui.console(log.entries, hidden={"info", "event"}, on_toggle_level=on_toggle),
             width=520, height=200)
```

```python
log = ui.Log()
log.write("info", "started")
log.write("error", "ValueError: bad input")
pz.container(ui.console(log.entries, query="error", on_query=on_query), width=520, height=140)
```

## Eventos

Consola tiene **8 eventos propios**, en la tabla de abajo. Como todo widget, también tiene los **18 eventos comunes** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), que se conectan con `.on(...)`: mira [Eventos](https://albakore.github.io/pyzed/es/guides/events/) y [Eventos de cada componente](https://albakore.github.io/pyzed/es/library/events/).

| Evento | De | El handler recibe | Cuándo |
| --- | --- | --- | --- |
| `on_query` | `ui.console` | `(text)` | Recibe cada edición del filtro. |
| `on_toggle_level` | `ui.console` | `(level)` | Se llama con el nivel del botón pulsado. |
| `on_clear` | `ui.console` | `()` | Añade el botón Limpiar. |
| `on_copy` | `ui.console` | `()` | Añade el botón Copiar. |
| `on_context` | `ui.console` | `(line)` | Se llama con la línea del clic derecho (`None` bajo las líneas): abre un menú ahí (copiar, ir al origen, limpiar...). |
| `on_open` | `ui.console` | `(line)` | Se llama con la línea del doble clic. |
| `on_source` | `ui.console` | `(source)` | Recibe el origen elegido en el desplegable (`None` para todos). |
| `on_write` | `ui.Log` | `()` | Se llama tras cada escritura (desde cualquier hilo). |

**El handler recibe** es lo que Pyzed le pasa a tu función: `()` es nada. Cualquier handler también puede ser un mensaje (un valor que no se puede llamar), que va a `update()`.

### Manejar los eventos

Los mismos eventos escritos de tres formas. **Imperativa**: métodos de la app que cambian `self`. **Declarativa**: los handlers producen mensajes y un único `update()` decide. **Funcional**: una función pura dibuja un estado inmutable y pide cambios con `set_state(...)`. Mira [Imperativo, declarativo y funcional](https://albakore.github.io/pyzed/es/guides/code-styles/).

**Imperativo:**

```python
class Debug(pz.App):
    def __init__(self):
        self.log = ui.Log()
        self.log.write("info", "Started")
        self.log.write("error", "ValueError: bad input")
        self.log.on_write = lambda: pz.post("log")          # a line arrived (from any thread)
        self.query, self.hidden = "", set()

    def update(self, message):
        pass                             # "log" just redraws

    def view(self):
        return ui.console(self.log.entries, query=self.query, hidden=self.hidden,
                          on_query=lambda q: setattr(self, "query", q),               # the filter
                          on_toggle_level=lambda lvl: self.hidden.symmetric_difference_update({lvl}),
                          on_clear=self.log.clear,                                    # adds Clear
                          on_copy=lambda: pz.Command.copy(self.log.text()),           # adds Copy
                          on_open=lambda entry: print("open", entry.text))            # double click
```

**Declarativo:**

```python
from dataclasses import dataclass

@dataclass
class Filter:
    query: str

@dataclass
class ToggleLevel:
    level: str

class Debug(pz.App):
    log = ui.Log()
    query, hidden = "", frozenset()

    def update(self, message):
        match message:
            case Filter(query):
                self.query = query
            case ToggleLevel(level):
                self.hidden = self.hidden ^ {level}
            case "clear":
                self.log.clear()

    def view(self):
        return ui.console(self.log.entries, query=self.query, hidden=self.hidden, on_query=Filter,
                          on_toggle_level=ToggleLevel, on_clear="clear")
```

**Funcional:**

```python
from dataclasses import dataclass

LOG = ui.Log()
LOG.write("info", "Started")

@dataclass(frozen=True)
class State:
    query: str = ""
    hidden: frozenset = frozenset()

def view(state, set_state):
    return ui.console(LOG.entries, query=state.query, hidden=state.hidden,
                      on_query=lambda q: set_state(query=q),
                      on_toggle_level=lambda lvl: set_state(hidden=state.hidden ^ {lvl}))
```

## Propiedades y API

### `ui.console`

```python
console(
    entries,
    *,
    query='',
    on_query=None,
    hidden=(),
    on_toggle_level=None,
    on_clear=None,
    on_copy=None,
    actions=(),
    selection=None,
    on_context=None,
    on_open=None,
    sources=(),
    source=None,
    on_source=None,
    show_source=False,
    placeholder=None,
    empty=None,
    font_size=12.5,
    id=None,
    theme=None,
)
```

Una vista de registro como la consola de las herramientas de desarrollo del navegador: filtro, un botón por nivel con su cuenta, copiar y limpiar, y las entradas en monoespaciada, siempre desplazada a la más nueva.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `entries` | *obligatorio* | Elementos `ui.LogEntry` (`log.entries`). |
| `query` | `""` | Texto del filtro. |
| `hidden` | `()` | Niveles ocultos. |
| `actions` | `()` | Widgets extra al final de la barra. |
| `selection` | `None` | Un `ui.LogSelection`: las líneas se pueden seleccionar con el ratón y una línea seleccionada con `detail` lo muestra debajo. |
| `sources` | `()` | Pares `(valor, etiqueta)`: añade un desplegable que muestra las líneas de un `source`. |
| `source` | `None` | El origen que se muestra (`None` para todos). |
| `show_source` | `False` | Etiqueta cada línea con su origen. |
| `placeholder` | `None` | Texto del filtro vacío. |
| `empty` | `None` | Lo que se ve mientras no hay líneas. |
| `font_size` | `12.5` | Tamaño de las entradas. |
| `id` | `None` | Id del scrollable. |
| `theme` | `None` | Tema a usar en lugar del actual (`pyzed.theme.current()`). |

Eventos: `on_query`, `on_toggle_level`, `on_clear`, `on_copy`, `on_context`, `on_open`, `on_source` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#console)

### `ui.Log`

```python
class Log
```

Las entradas de un `ui.console`: seguro entre hilos, limitado a `limit` y con las líneas repetidas agrupadas. `write(level, text)` añade una línea; `capture()` (o `with log:`) copia la salida de `print` y los tracebacks; `pipe(stream)` lee la salida de un subproceso en segundo plano; `text()` devuelve todo para copiarlo (`text(ids)` solo esas líneas). `write` también acepta el `source` de la línea, su `detail` (un traceback) y su `origin` (`(ruta, línea)`) y devuelve la entrada; `find(id)` busca una.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `limit` | `5000` | Máximo de entradas guardadas. |

Eventos: `on_write` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#log)

### `ui.LogEntry`

```python
@dataclass
class LogEntry(level, text, time=time.time(), count=1, source='', detail='', origin=None, id=0)
```

Una línea de un `ui.Log`: `level`, `text`, `time`, `count` (líneas idénticas seguidas), `source`, `detail`, `origin`, su número `id`, `clock` (`HH:MM:SS`) y `line()` (la entrada como texto).

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `level` | *obligatorio* | Nivel. |
| `text` | *obligatorio* | La línea. |
| `time` | `time()` | Cuándo se escribió (`time.time()`). |
| `count` | `1` | Líneas idénticas seguidas. |
| `source` | `""` | De dónde viene (`"project"`, `"agent"`...); `ui.console` puede mostrarlo y filtrarlo. |
| `detail` | `""` | Más texto que se ve bajo la línea mientras está seleccionada (un traceback). |
| `origin` | `None` | `(ruta, línea)` del código al que se refiere. |
| `id` | `0` | Su número en el log (lo que guarda un `ui.LogSelection`). |

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#logentry)

### `ui.LogSelection`

```python
class LogSelection
```

Las líneas seleccionadas de un `ui.console`, como en una lista de archivos: un clic selecciona una línea, Shift+clic un rango desde la última pulsada y Ctrl+clic (Cmd en macOS) añade o quita una. Guarda una en tu app y pásala a `ui.console(selection=...)`; `select(ids)` selecciona justo esas (Ctrl+A), `clear()` ninguna, `keep(entries)` olvida las líneas que ya no están e `ids` son los `id` de las seleccionadas para `log.text(ids)`.

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#logselection)

### `ui.LOG_LEVELS`

Los niveles que colorea `ui.console`, en el orden de sus filtros: debug, info, event, stdout, stderr, warning, error.

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#log_levels)

## Relacionados

- [Terminal](https://albakore.github.io/pyzed/es/library/terminal/): Una vista de terminal de solo lectura con colores ANSI.
- [Editor](https://albakore.github.io/pyzed/es/library/editor/): Un editor de código completo: resaltado, plegado, minimapa, marcas de git.
