diff --git a/tabulate.py b/tabulate.py
index 5d57167..cbeb766 100755
--- a/tabulate.py
+++ b/tabulate.py
@@ -535,10 +535,10 @@ multiline_formats = {
 _multiline_codes = re.compile(r"\r|\n|\r\n")
 _multiline_codes_bytes = re.compile(b"\r|\n|\r\n")
 _invisible_codes = re.compile(
-    r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m"
+    r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m|\x1b\]8;;(.*?)\x1b\\"
 )  # ANSI color codes
 _invisible_codes_bytes = re.compile(
-    b"\x1b\\[\\d+\\[;\\d]*m|\x1b\\[\\d*;\\d*;\\d*m"
+    b"\x1b\\[\\d+\\[;\\d]*m|\x1b\\[\\d*;\\d*;\\d*m|\\x1b\\]8;;(.*?)\\x1b\\\\"
 )  # ANSI color codes
 
 

Support hyperlinks encoded as terminal escape sequences
Some terminals support [clickable text](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) via escape sequences. If we use the escape sequence

```python
>>> import tabulate
>>>
>>> def encode_url(text: str, target:str):
...     return f"\u001b]8;;{target}\u001b\\{text}\u001b]8;;\u001b\\"
... 
>>> table = tabulate.tabulate([[encode_url("test", "http://example.org")]], headers=["test"], tablefmt="grid")
>>> print(table)
+--------+
| test   |
+========+
| test        |
+--------+
```

tabulate does not calculate the width correctly.
diff --git a/test/test_output.py b/test/test_output.py
index 0e72c71..535a6b0 100644
--- a/test/test_output.py
+++ b/test/test_output.py
@@ -59,6 +59,22 @@ def test_plain_multiline():
     assert_equal(expected, result)
 
 
+def test_plain_multiline_with_links():
+    "Output: plain with multiline cells with links and headers"
+    table = [[2, "foo\nbar"]]
+    headers = ("more\nspam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\", "more spam\n& eggs")
+    expected = "\n".join(
+        [
+            "       more  more spam",
+            "  spam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\  & eggs",
+            "          2  foo",
+            "             bar",
+        ]
+    )
+    result = tabulate(table, headers, tablefmt="plain")
+    assert_equal(expected, result)
+
+
 def test_plain_multiline_with_empty_cells():
     "Output: plain with multiline cells and empty cells with headers"
     table = [
@@ -162,6 +178,23 @@ def test_simple_multiline():
     assert_equal(expected, result)
 
 
+def test_simple_multiline_with_links():
+    "Output: simple with multiline cells with links and headers"
+    table = [[2, "foo\nbar"]]
+    headers = ("more\nspam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\", "more spam\n& eggs")
+    expected = "\n".join(
+        [
+            "       more  more spam",
+            "  spam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\  & eggs",
+            "-----------  -----------",
+            "          2  foo",
+            "             bar",
+        ]
+    )
+    result = tabulate(table, headers, tablefmt="simple")
+    assert_equal(expected, result)
+
+
 def test_simple_multiline_with_empty_cells():
     "Output: simple with multiline cells and empty cells with headers"
     table = [
@@ -766,6 +799,25 @@ def test_pretty_multiline():
     assert_equal(expected, result)
 
 
+def test_pretty_multiline_with_links():
+    "Output: pretty with multiline cells with headers"
+    table = [[2, "foo\nbar"]]
+    headers = ("more\nspam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\", "more spam\n& eggs")
+    expected = "\n".join(
+        [
+            "+-----------+-----------+",
+            "|   more    | more spam |",
+            "| spam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\ |  & eggs   |",
+            "+-----------+-----------+",
+            "|     2     |    foo    |",
+            "|           |    bar    |",
+            "+-----------+-----------+",
+        ]
+    )
+    result = tabulate(table, headers, tablefmt="pretty")
+    assert_equal(expected, result)
+
+
 def test_pretty_multiline_with_empty_cells():
     "Output: pretty with multiline cells and empty cells with headers"
     table = [
@@ -889,6 +941,25 @@ def test_rst_multiline():
     assert_equal(expected, result)
 
 
+def test_rst_multiline_with_links():
+    "Output: rst with multiline cells with headers"
+    table = [[2, "foo\nbar"]]
+    headers = ("more\nspam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\", "more spam\n& eggs")
+    expected = "\n".join(
+        [
+            "===========  ===========",
+            "       more  more spam",
+            "  spam \x1b]8;;target\x1b\\eggs\x1b]8;;\x1b\\  & eggs",
+            "===========  ===========",
+            "          2  foo",
+            "             bar",
+            "===========  ===========",
+        ]
+    )
+    result = tabulate(table, headers, tablefmt="rst")
+    assert_equal(expected, result)
+
+
 def test_rst_multiline_with_empty_cells():
     "Output: rst with multiline cells and empty cells with headers"
     table = [
diff --git a/test/test_regression.py b/test/test_regression.py
index 2324c06..955e11f 100644
--- a/test/test_regression.py
+++ b/test/test_regression.py
@@ -47,6 +47,52 @@ def test_alignment_of_colored_cells():
     assert_equal(expected, formatted)
 
 
+def test_alignment_of_link_cells():
+    "Regression: Align links as if they were colorless."
+    linktable = [
+        ("test", 42, "\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\"),
+        ("test", 101, "\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\"),
+    ]
+    linkheaders = ("test", "\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\", "test")
+    formatted = tabulate(linktable, linkheaders, "grid")
+    expected = "\n".join(
+        [
+            "+--------+--------+--------+",
+            "| test   |   \x1b]8;;target\x1b\\test\x1b]8;;\x1b\\ | test   |",
+            "+========+========+========+",
+            "| test   |     42 | \x1b]8;;target\x1b\\test\x1b]8;;\x1b\\   |",
+            "+--------+--------+--------+",
+            "| test   |    101 | \x1b]8;;target\x1b\\test\x1b]8;;\x1b\\   |",
+            "+--------+--------+--------+",
+        ]
+    )
+    print("expected: %r\n\ngot:      %r\n" % (expected, formatted))
+    assert_equal(expected, formatted)
+
+
+def test_alignment_of_link_text_cells():
+    "Regression: Align links as if they were colorless."
+    linktable = [
+        ("test", 42, "1\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\2"),
+        ("test", 101, "3\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\4"),
+    ]
+    linkheaders = ("test", "5\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\6", "test")
+    formatted = tabulate(linktable, linkheaders, "grid")
+    expected = "\n".join(
+        [
+            "+--------+----------+--------+",
+            "| test   |   5\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\6 | test   |",
+            "+========+==========+========+",
+            "| test   |       42 | 1\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\2 |",
+            "+--------+----------+--------+",
+            "| test   |      101 | 3\x1b]8;;target\x1b\\test\x1b]8;;\x1b\\4 |",
+            "+--------+----------+--------+",
+        ]
+    )
+    print("expected: %r\n\ngot:      %r\n" % (expected, formatted))
+    assert_equal(expected, formatted)
+
+
 def test_iter_of_iters_with_headers():
     "Regression: Generator of generators with a gen. of headers (issue #9)."
 

pip install -e .[widechars]
parse_log_pytest
pytest
pytest wcwidth
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
packaging @ file:///croot/packaging_1720101850331/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042571233/work
pytest @ file:///croot/pytest_1717793244625/work
-e git+https://github.com/astanin/python-tabulate.git@2552e6dfb23cac990aeabb27b86745878d62247e#egg=tabulate
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
wcwidth==0.2.13

name: python-tabulate
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py38h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.1=py38h06a4308_0
  - pip=24.2=py38h06a4308_0
  - pluggy=1.0.0=py38h06a4308_1
  - pytest=7.4.4=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py38h06a4308_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - wcwidth==0.2.13
prefix: /opt/conda/envs/python-tabulate

test/test_output.py::test_plain_multiline_with_links test/test_output.py::test_simple_multiline_with_links test/test_output.py::test_pretty_multiline_with_links test/test_output.py::test_rst_multiline_with_links test/test_regression.py::test_alignment_of_link_cells test/test_regression.py::test_alignment_of_link_text_cells
test/test_output.py::test_plain test/test_output.py::test_plain_headerless test/test_output.py::test_plain_multiline_headerless test/test_output.py::test_plain_multiline test/test_output.py::test_plain_multiline_with_empty_cells test/test_output.py::test_plain_multiline_with_empty_cells_headerless test/test_output.py::test_simple test/test_output.py::test_simple_multiline_2 test/test_output.py::test_simple_headerless test/test_output.py::test_simple_multiline_headerless test/test_output.py::test_simple_multiline test/test_output.py::test_simple_multiline_with_empty_cells test/test_output.py::test_simple_multiline_with_empty_cells_headerless test/test_output.py::test_github test/test_output.py::test_grid test/test_output.py::test_grid_wide_characters test/test_output.py::test_grid_headerless test/test_output.py::test_grid_multiline_headerless test/test_output.py::test_grid_multiline test/test_output.py::test_grid_multiline_with_empty_cells test/test_output.py::test_grid_multiline_with_empty_cells_headerless test/test_output.py::test_fancy_grid test/test_output.py::test_fancy_grid_headerless test/test_output.py::test_fancy_grid_multiline_headerless test/test_output.py::test_fancy_grid_multiline test/test_output.py::test_fancy_grid_multiline_with_empty_cells test/test_output.py::test_fancy_grid_multiline_with_empty_cells_headerless test/test_output.py::test_pipe test/test_output.py::test_pipe_headerless test/test_output.py::test_presto test/test_output.py::test_presto_headerless test/test_output.py::test_presto_multiline_headerless test/test_output.py::test_presto_multiline test/test_output.py::test_presto_multiline_with_empty_cells test/test_output.py::test_presto_multiline_with_empty_cells_headerless test/test_output.py::test_orgtbl test/test_output.py::test_orgtbl_headerless test/test_output.py::test_psql test/test_output.py::test_psql_headerless test/test_output.py::test_psql_multiline_headerless test/test_output.py::test_psql_multiline test/test_output.py::test_psql_multiline_with_empty_cells test/test_output.py::test_psql_multiline_with_empty_cells_headerless test/test_output.py::test_pretty test/test_output.py::test_pretty_headerless test/test_output.py::test_pretty_multiline_headerless test/test_output.py::test_pretty_multiline test/test_output.py::test_pretty_multiline_with_empty_cells test/test_output.py::test_pretty_multiline_with_empty_cells_headerless test/test_output.py::test_jira test/test_output.py::test_jira_headerless test/test_output.py::test_rst test/test_output.py::test_rst_with_empty_values_in_first_column test/test_output.py::test_rst_headerless test/test_output.py::test_rst_multiline test/test_output.py::test_rst_multiline_with_empty_cells test/test_output.py::test_rst_multiline_with_empty_cells_headerless test/test_output.py::test_mediawiki test/test_output.py::test_mediawiki_headerless test/test_output.py::test_moinmoin test/test_output.py::test_youtrack test/test_output.py::test_moinmoin_headerless test/test_output.py::test_html test/test_output.py::test_unsafehtml test/test_output.py::test_html_headerless test/test_output.py::test_unsafehtml_headerless test/test_output.py::test_latex test/test_output.py::test_latex_raw test/test_output.py::test_latex_headerless test/test_output.py::test_latex_booktabs test/test_output.py::test_latex_booktabs_headerless test/test_output.py::test_textile test/test_output.py::test_textile_with_header test/test_output.py::test_textile_with_center_align test/test_output.py::test_no_data test/test_output.py::test_empty_data test/test_output.py::test_no_data_without_headers test/test_output.py::test_empty_data_without_headers test/test_output.py::test_floatfmt test/test_output.py::test_floatfmt_multi test/test_output.py::test_colalign_multi test/test_output.py::test_float_conversions test/test_output.py::test_missingval test/test_output.py::test_missingval_multi test/test_output.py::test_column_alignment test/test_output.py::test_unaligned_separated test/test_output.py::test_dict_like_with_index test/test_output.py::test_list_of_lists_with_index test/test_output.py::test_list_of_lists_with_supplied_index test/test_output.py::test_list_of_lists_with_index_firstrow test/test_output.py::test_disable_numparse_default test/test_output.py::test_disable_numparse_true test/test_output.py::test_disable_numparse_list test/test_output.py::test_preserve_whitespace test/test_regression.py::test_ansi_color_in_table_cells test/test_regression.py::test_alignment_of_colored_cells test/test_regression.py::test_iter_of_iters_with_headers test/test_regression.py::test_datetime_values test/test_regression.py::test_simple_separated_format test/test_regression.py::test_simple_separated_format_with_headers test/test_regression.py::test_column_type_of_bytestring_columns test/test_regression.py::test_numeric_column_headers test/test_regression.py::test_88_256_ANSI_color_codes test/test_regression.py::test_column_with_mixed_value_types test/test_regression.py::test_latex_escape_special_chars test/test_regression.py::test_isconvertible_on_set_values test/test_regression.py::test_ansi_color_for_decimal_numbers test/test_regression.py::test_alignment_of_decimal_numbers_with_ansi_color test/test_regression.py::test_long_integers test/test_regression.py::test_colorclass_colors test/test_regression.py::test_mix_normal_and_wide_characters test/test_regression.py::test_align_long_integers test/test_regression.py::test_boolean_columns test/test_regression.py::test_ansi_color_bold_and_fgcolor test/test_regression.py::test_empty_table_with_keys_as_header test/test_regression.py::test_escape_empty_cell_in_first_column_in_rst test/test_regression.py::test_ragged_rows test/test_regression.py::test_empty_pipe_table_with_columns test/test_regression.py::test_custom_tablefmt
tabulate.py
tabulate.py