Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f783e96
Add modern cli-styled print output for graphs, vertex sequences, and …
schochastics May 29, 2026
0e92d6a
chore: Auto-update from GitHub Actions
schochastics May 29, 2026
001327c
Replace literal Unicode characters with escape sequences in print.R
schochastics May 30, 2026
c0d8176
address review comments
schochastics Jun 2, 2026
c57181a
Merge remote-tracking branch 'origin/main' into feat/print-style-modern
schochastics Jun 2, 2026
b2818ca
chore: Auto-update from GitHub Actions
schochastics Jun 2, 2026
e8a2fcd
Remove fixed-width padding of edge endpoints in plain edge list printing
schochastics Jun 4, 2026
2a2277c
Merge remote-tracking branch 'origin/main' into feat/print-style-modern
schochastics Jun 4, 2026
74fc03c
chore: Auto-update from GitHub Actions
schochastics Jun 4, 2026
9528357
Merge branch 'main' into feat/print-style-modern
schochastics Jun 4, 2026
46bc320
make cli default print and fix unnecessary blank lines
schochastics Jun 4, 2026
1e41d56
Document print.style option for cli-styled output in print.igraph man…
schochastics Jun 4, 2026
9b2de7c
Fix whitespace in `make_graph` formula examples in documentation
schochastics Jun 4, 2026
a72b769
Improve comments and robustness of vertex/edge sequence printing
schochastics Jun 4, 2026
97d642d
Reformat if-else expression in print_igraph_header_cli for readability
schochastics Jun 4, 2026
defa9c0
Refactor `attr_label_cli` and attribute summary formatting to use cli…
schochastics Jun 4, 2026
f68ce31
Refactor CLI print helpers: extract shared utilities for section rule…
schochastics Jun 4, 2026
ee520ab
Pin print options in test setup; split classic print tests out
schochastics Jun 4, 2026
eb4c196
Extract classic vs/es printing into *_legacy dispatch functions
schochastics Jun 4, 2026
aa8609f
Extract classic graph printing into *_legacy dispatch functions
schochastics Jun 4, 2026
109182d
chore: Auto-update from GitHub Actions
schochastics Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions R/iterators.R
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,19 @@ print.igraph.vs <- function(
full = igraph_opt("print.full"),
id = igraph_opt("print.id"),
...
) {
if (!is_cli_style()) {
return(print_igraph_vs_legacy(x, full = full, id = id, ...))
}

print_igraph_vs_cli(x, full = full, id = id, ...)
}

print_igraph_vs_legacy <- function(
x,
full = igraph_opt("print.full"),
id = igraph_opt("print.id"),
...
) {
graph <- get_vs_graph(x)
if (!is.null(graph)) {
Expand Down Expand Up @@ -1538,6 +1551,19 @@ print.igraph.es <- function(
full = igraph_opt("print.full"),
id = igraph_opt("print.id"),
...
) {
if (!is_cli_style()) {
return(print_igraph_es_legacy(x, full = full, id = id, ...))
}

print_igraph_es_cli(x, full = full, id = id, ...)
}

print_igraph_es_legacy <- function(
x,
full = igraph_opt("print.full"),
id = igraph_opt("print.id"),
...
) {
graph <- get_es_graph(x)
ml <- if (identical(full, TRUE)) NULL else igraph_opt("auto.print.lines")
Expand All @@ -1552,6 +1578,161 @@ print.igraph.es <- function(
invisible(x)
}

# cli-styled printing for vertex/edge sequences -----------------

print_igraph_vs_cli <- function(
x,
full = igraph_opt("print.full"),
id = igraph_opt("print.id"),
...
) {
graph <- get_vs_graph(x)
vertices <- if (!is.null(graph)) V(graph) else NULL
total <- if (is.null(vertices)) "?" else as.character(length(vertices))
Comment thread
schochastics marked this conversation as resolved.
graph_id_short <- graph_id(x)

middot <- middot_cli()
# `id` comes from an option and may be NULL/NA, so guard with isTRUE().
flags <- c(
if (!is.null(vertices) && !is.null(names(vertices))) "named",
if (isTRUE(id) && !is.na(graph_id_short)) {
paste("from", substr(graph_id_short, 1, 7))
},
if (is.null(graph)) "deleted"
)

title <- paste0(
"<vertex sequence> ",
length(x),
"/",
total,
flag_suffix_cli(flags, middot)
)
cli_section(title, blank = FALSE)

# A vertex sequence prints in one of two layouts:
#
# * detailed -- one row per selected vertex with every vertex attribute as
# a column (a metadata table); produced by `V(g)[[...]]`.
# * compact -- a bare list of vertex ids, or names when present; produced
# by `V(g)[...]`.
#
# `[[` and `[` build the *same* underlying sequence: `[[.igraph.vs` merely
# tags its result with a "single" attribute, which is_single_index() reads
# here. So the only signal asking for the detailed view is that flag. We use
# the table only when it is set AND the graph is still alive (a sequence can
# outlive its graph) AND the graph actually has attributes to tabulate;
# anything else falls through to the compact list below.
if (
Comment thread
schochastics marked this conversation as resolved.
is_single_index(x) &&
!is.null(graph) &&
length(vertex_attr_names(graph)) > 0
) {
vertex_attrs <- vertex_attr(graph)
# A data.frame needs flat columns, so it works only when every attribute is
# atomic. If any attribute is list-valued, drop to a named list sliced to
# the selected vertices instead of forcing it into a table.
if (all(vapply(vertex_attrs, is.atomic, logical(1)))) {
print(as.data.frame(vertex_attrs, stringsAsFactors = FALSE)[
as.vector(x),
,
drop = FALSE
])
} else {
print(lapply(vertex_attrs, "[", as.vector(x)))
}
} else {
# Otherwise just list the vertices (by name when available).
if (!is.null(names(vertices))) {
x2 <- names(vertices)[as.vector(x)]
if (!is.null(names(x)) && !identical(names(x), x2)) {
names(x2) <- names(x)
}
} else {
x2 <- as.vector(x)
}
if (length(x2)) {
max_lines <- if (isTRUE(full)) NULL else igraph_opt("auto.print.lines")
print_cli_lines(x2, max_lines, "+ ... omitted several vertices\n")
}
}

invisible(x)
}

print_igraph_es_cli <- function(
x,
full = igraph_opt("print.full"),
id = igraph_opt("print.id"),
...
) {
graph <- get_es_graph(x)
total <- if (is.null(graph)) "?" else as.character(gsize(graph))
graph_id_short <- graph_id(x)
has_vnames <- !is.null(attr(x, "vnames"))

middot <- middot_cli()
# `id` comes from an option and may be NULL/NA, so guard with isTRUE().
flags <- c(
if (has_vnames) "vertex names",
if (isTRUE(id) && !is.na(graph_id_short)) {
paste("from", substr(graph_id_short, 1, 7))
},
if (is.null(graph)) "deleted"
)

title <- paste0(
"<edge sequence> ",
length(x),
"/",
total,
flag_suffix_cli(flags, middot)
)
cli_section(title, blank = FALSE)

if (length(x) == 0) {
return(invisible(x))
}

# An edge sequence prints in one of two layouts, mirroring vertex sequences:
#
# * detailed -- one row per selected edge with its endpoints and every edge
# attribute as columns (a metadata table); produced by `E(g)[[...]]`.
# * compact -- a list of "tail <arrow> head" strings; produced by
# `E(g)[...]`. Handled further below.
#
# As with vertex sequences, `[[` and `[` build the same underlying sequence;
# `[[.igraph.es` only tags its result with the "single" attribute that
# is_single_index() reads here. The table needs only that flag and a live
# graph -- unlike the vertex case there is no attribute-count check, because
# an edge always has endpoints to tabulate (the tail/head names plus their
# raw numeric ids in tid/hid), so the table is never empty.
if (is_single_index(x) && !is.null(graph)) {
print_edge_detail(graph, x)
return(invisible(x))
}

max_lines <- if (isTRUE(full)) NULL else igraph_opt("auto.print.lines")

if (!is.null(graph)) {
# Live graph: render endpoints with arrows.
arrow <- edge_arrow_cli(is_directed(graph))
endpoints <- ends(graph, x, names = has_vnames || is_named(graph))
body <- format_cli_edge_endpoints(endpoints, arrow)
} else {
# Graph was deleted: fall back to stored vertex names / ids.
body <- if (!is.null(attr(x, "vnames"))) {
as.vector(attr(x, "vnames"))
} else if (!is.null(names(x))) {
names(x)
} else {
as.vector(x)
}
}
print_cli_lines(body, max_lines, "+ ... omitted several edges\n")
invisible(x)
}

# these are internal

as_igraph_vs <- function(graph, v, na.ok = FALSE) {
Expand Down
11 changes: 10 additions & 1 deletion R/par.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ getIgraphOpt <- function(x, default = NULL) {
"annotate.plot" = FALSE,
"auto.print.lines" = 10,
"return.vs.es" = TRUE,
"print.id" = TRUE
"print.id" = TRUE,
"print.style" = "cli"
)

igraph.pars.set.verbose <- function(verbose) {
Expand Down Expand Up @@ -156,6 +157,14 @@ igraph.pars.callbacks <- list("verbose" = igraph.pars.set.verbose)
#' \item{print.vertex.attributes}{
#' Logical constant, whether to print vertex attributes when printing graphs. Defaults to `FALSE`.
#' }
#' \item{print.style}{
#' Character string controlling the visual style used by
#' [print.igraph()], [summary.igraph()], [print.igraph.vs()] and
#' [print.igraph.es()]. Possible values are `"cli"` (default, a
#' cli-styled output with section rules, Unicode arrows for edges and
#' typed attribute listings) and `"classic"` (the historical
#' `IGRAPH ... DNW-` header relied on by tutorials and parsers).
#' }
#' \item{return.vs.es}{
#' Whether functions that return a set or sequence of vertices/edges
#' should return formal vertex/edge sequence objects.
Expand Down
Loading