ghosttea-vt-sys 0.10.0

Native linking contract for Ghosttea's pinned Ghostty VT core
#include "ghostty_shim_internal.h"

#include <stdint.h>

static int eg_emit_hyperlink_identity(
    EgTerminal* state,
    const GhosttyGridRef* ref,
    uint32_t row,
    uint16_t column,
    uint16_t span,
    EgHyperlinkIdentityFn identity_fn,
    void* userdata) {
  GhosttyHyperlinkIdentity identity =
      GHOSTTY_INIT_SIZED(GhosttyHyperlinkIdentity);
  size_t id_len = 0;
  GhosttyResult result = ghostty_grid_ref_hyperlink_identity(
      ref, NULL, 0, &id_len, &identity);
  if (result != GHOSTTY_SUCCESS && result != GHOSTTY_OUT_OF_SPACE)
    return result;
  if (identity.tag == GHOSTTY_HYPERLINK_IDENTITY_NONE)
    return GHOSTTY_SUCCESS;
  if (identity.tag != GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT &&
      identity.tag != GHOSTTY_HYPERLINK_IDENTITY_IMPLICIT)
    return GHOSTTY_INVALID_VALUE;
  if (identity.tag == GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT && id_len != 0) {
    if (!eg_buffer_reserve(&state->hyperlink_id, id_len))
      return GHOSTTY_OUT_OF_MEMORY;
    result = ghostty_grid_ref_hyperlink_identity(
        ref, state->hyperlink_id.ptr, state->hyperlink_id.cap, &id_len,
        &identity);
    if (result != GHOSTTY_SUCCESS) return result;
  }
  identity_fn(
      userdata, row, column, span, (uint8_t)identity.tag,
      identity.tag == GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT
          ? state->hyperlink_id.ptr
          : NULL,
      identity.tag == GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT ? id_len : 0,
      identity.implicit_token);
  return GHOSTTY_SUCCESS;
}

int eg_terminal_screen_hyperlink_identities(
    EgTerminal* state,
    uint8_t screen,
    EgHyperlinkIdentityFn identity_fn,
    void* userdata) {
  if (state == NULL || identity_fn == NULL || screen > 1) return -1;
  GhosttyTerminalScreenState screen_state =
      GHOSTTY_INIT_SIZED(GhosttyTerminalScreenState);
  GhosttyResult result = ghostty_terminal_screen_get(
      state->terminal, (GhosttyTerminalScreen)screen, &screen_state);
  if (result == GHOSTTY_NO_VALUE) return 0;
  if (result != GHOSTTY_SUCCESS) return -1;
  GhosttyTerminalScreenRowIterator iterator = NULL;
  result = ghostty_terminal_screen_row_iterator_new(
      NULL, state->terminal, (GhosttyTerminalScreen)screen, &iterator);
  if (result == GHOSTTY_NO_VALUE) return 0;
  if (result != GHOSTTY_SUCCESS) return -1;

  uint64_t row = 0;
  int status = -1;
  for (;;) {
    GhosttyGridRef row_ref = GHOSTTY_INIT_SIZED(GhosttyGridRef);
    bool has_row = false;
    result = ghostty_terminal_screen_row_iterator_next(
        iterator, &row_ref, &has_row);
    if (result != GHOSTTY_SUCCESS) goto identity_cleanup;
    if (!has_row) break;
    if (row > UINT32_MAX) goto identity_cleanup;
    for (uint16_t column = 0; column < screen_state.cols; column++) {
      GhosttyGridRef ref = row_ref;
      ref.x = column;
      GhosttyCell cell = 0;
      result = ghostty_grid_ref_cell(&ref, &cell);
      if (result != GHOSTTY_SUCCESS) goto identity_cleanup;
      bool has_hyperlink = false;
      result = ghostty_cell_get(
          cell, GHOSTTY_CELL_DATA_HAS_HYPERLINK, &has_hyperlink);
      if (result != GHOSTTY_SUCCESS) goto identity_cleanup;
      if (!has_hyperlink) continue;
      GhosttyCellWide wide = GHOSTTY_CELL_WIDE_NARROW;
      result = ghostty_cell_get(cell, GHOSTTY_CELL_DATA_WIDE, &wide);
      if (result != GHOSTTY_SUCCESS) goto identity_cleanup;
      if (wide == GHOSTTY_CELL_WIDE_SPACER_TAIL ||
          wide == GHOSTTY_CELL_WIDE_SPACER_HEAD)
        continue;
      const uint16_t span = wide == GHOSTTY_CELL_WIDE_WIDE ? 2 : 1;
      result = eg_emit_hyperlink_identity(
          state, &ref, (uint32_t)row, column, span, identity_fn, userdata);
      if (result != GHOSTTY_SUCCESS) goto identity_cleanup;
    }
    row += 1;
  }
  if (row != screen_state.total_rows) goto identity_cleanup;
  status = 1;

identity_cleanup:
  ghostty_terminal_screen_row_iterator_free(iterator);
  return status;
}

int eg_terminal_screen_cursor_hyperlink(
    EgTerminal* state,
    uint8_t screen,
    EgCursorHyperlinkFn hyperlink_fn,
    void* userdata) {
  if (state == NULL || hyperlink_fn == NULL || screen > 1) return -1;
  GhosttyHyperlinkIdentity identity =
      GHOSTTY_INIT_SIZED(GhosttyHyperlinkIdentity);
  size_t uri_len = 0;
  size_t id_len = 0;
  GhosttyResult result = ghostty_terminal_screen_cursor_hyperlink(
      state->terminal, (GhosttyTerminalScreen)screen,
      NULL, 0, &uri_len, NULL, 0, &id_len, &identity);
  if (result == GHOSTTY_NO_VALUE) return 0;
  if (result != GHOSTTY_SUCCESS && result != GHOSTTY_OUT_OF_SPACE) return -1;
  if (identity.tag != GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT &&
      identity.tag != GHOSTTY_HYPERLINK_IDENTITY_IMPLICIT)
    return -1;
  if (!eg_buffer_reserve(&state->hyperlink_uri, uri_len) ||
      !eg_buffer_reserve(&state->hyperlink_id, id_len))
    return -1;
  result = ghostty_terminal_screen_cursor_hyperlink(
      state->terminal, (GhosttyTerminalScreen)screen,
      state->hyperlink_uri.ptr, state->hyperlink_uri.cap, &uri_len,
      state->hyperlink_id.ptr, state->hyperlink_id.cap, &id_len, &identity);
  if (result != GHOSTTY_SUCCESS) return -1;
  hyperlink_fn(
      userdata, state->hyperlink_uri.ptr, uri_len, (uint8_t)identity.tag,
      identity.tag == GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT
          ? state->hyperlink_id.ptr
          : NULL,
      identity.tag == GHOSTTY_HYPERLINK_IDENTITY_EXPLICIT ? id_len : 0,
      identity.implicit_token);
  return 1;
}