From 1a163d0ce9e723e9011dc3cd875fbff05bd72387 Mon Sep 17 00:00:00 2001 From: Jérôme Duval Date: Wed, 01 Oct 2025 17:47:45 +0200 Subject: [PATCH] Terminal: support for hyperlinks (#18827) * support for invisible text * support for DECSTR - soft terminal reset Change-Id: I5cc333c7409a16fb41bcd57c5ebf2f277bd31370 Reviewed-on: https://review.haiku-os.org/c/haiku/+/9661 Reviewed-by: Adrien Destugues Tested-by: Commit checker robot --- src/apps/terminal/HyperLink.cpp | 23 ++++++++++++++++++++++- src/apps/terminal/HyperLink.h | 8 +++++++- src/apps/terminal/TermConst.h | 3 ++- src/apps/terminal/TermParse.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/apps/terminal/TermView.cpp | 5 ++++- src/apps/terminal/TermView.h | 2 +- src/apps/terminal/TermViewStates.cpp | 13 +++++++++++-- src/apps/terminal/TerminalBuffer.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ src/apps/terminal/TerminalBuffer.h | 13 +++++++++++++ src/apps/terminal/TerminalLine.h | 25 +++++++++++++++++++++++-- src/apps/terminal/VTPrsTbl.c | 4 ++-- src/apps/terminal/VTparse.h | 2 ++ 12 files changed, 173 insertions(+), 16 deletions(-) diff --git a/src/apps/terminal/HyperLink.cpp b/src/apps/terminal/HyperLink.cpp index e3eab28..f3f21e0 100644 --- a/src/apps/terminal/HyperLink.cpp +++ b/src/apps/terminal/HyperLink.cpp @@ -15,7 +15,9 @@ HyperLink::HyperLink() : fAddress(), - fType(TYPE_URL) + fType(TYPE_URL), + fOSCRef(0), + fOSCID(NULL) { } @@ -24,7 +26,9 @@ : fText(address), fAddress(address), - fType(type) + fType(type), + fOSCRef(0), + fOSCID(NULL) { } @@ -33,7 +37,20 @@ : fText(text), fAddress(address), - fType(type) + fType(type), + fOSCRef(0), + fOSCID(NULL) +{ +} + + +HyperLink::HyperLink(const BString& address, uint32 ref, const BString& id) + : + fText(NULL), + fAddress(address), + fType(TYPE_OSC_URL), + fOSCRef(ref), + fOSCID(id) { } diff --git a/src/apps/terminal/HyperLink.h b/src/apps/terminal/HyperLink.h index ba7d58a..c21dca1 100644 --- a/src/apps/terminal/HyperLink.h +++ b/src/apps/terminal/HyperLink.h @@ -15,7 +15,8 @@ TYPE_URL, TYPE_PATH, TYPE_PATH_WITH_LINE, - TYPE_PATH_WITH_LINE_AND_COLUMN + TYPE_PATH_WITH_LINE_AND_COLUMN, + TYPE_OSC_URL }; public: @@ -23,12 +24,15 @@ HyperLink(const BString& address, Type type); HyperLink(const BString& text, const BString& address, Type type); + HyperLink(const BString& address, uint32 ref, const BString& id); bool IsValid() const { return !fAddress.IsEmpty(); } const BString& Text() const { return fText; } const BString& Address() const { return fAddress; } Type GetType() const { return fType; } + const BString& OSCID() const { return fOSCID; } + const uint32& OSCRef() const { return fOSCRef; } status_t Open(); @@ -36,6 +40,8 @@ BString fText; BString fAddress; Type fType; + uint32 fOSCRef; + BString fOSCID; }; diff --git a/src/apps/terminal/TermConst.h b/src/apps/terminal/TermConst.h index 7d2bdf4..0620019 100644 --- a/src/apps/terminal/TermConst.h +++ b/src/apps/terminal/TermConst.h @@ -220,9 +220,10 @@ #define DUMPCR 0x0040 #define UNDERSET 0x0020 #define OVERLINE 0x0010 +#define HIDDEN 0x0008 #define FORECOLOR 0xFF0000 #define BACKCOLOR 0xFF000000 -#define CHAR_ATTRIBUTES 0xFFFF7730 +#define CHAR_ATTRIBUTES 0xFFFF7738 #define FORECOLORED(x) ((x) << 16) #define BACKCOLORED(x) ((x) << 24) diff --git a/src/apps/terminal/TermParse.cpp b/src/apps/terminal/TermParse.cpp index 269a630..fd87ede 100644 --- a/src/apps/terminal/TermParse.cpp +++ b/src/apps/terminal/TermParse.cpp @@ -646,6 +646,12 @@ param[nparam++] = ' '; break; + case CASE_CSI_EXCL: // ESC [N p + // part of soft reset DECSTR + if (nparam < NPARAM) + param[nparam++] = '!'; + break; + case CASE_DEC_DOL: // ESC [? N p // part of change cursor style DECRQM if (nparam < NPARAM) @@ -836,6 +842,10 @@ case 7: /* Inverse */ attributes |= INVERSE; + break; + + case 8: /* Hidden */ + attributes |= HIDDEN; break; case 21: /* Double Underline */ @@ -852,6 +862,10 @@ case 27: /* Not Inverse */ attributes &= ~INVERSE; + break; + + case 28: /* Not Hidden */ + attributes &= ~HIDDEN; break; case 53: /* Overline */ @@ -1294,6 +1308,14 @@ case CASE_DECRQM: // DECRQM - request mode to terminal if (nparam == 2 && param[1] == '$') { _DecPrivateModeRequest(param[0]); + } + parsestate = groundtable; + break; + + case CASE_DECSTR: // DECSTR - soft terminal reset + if (nparam == 2 && param[1] == '!') { + Attributes attributes; + fBuffer->SetAttributes(attributes); } parsestate = groundtable; break; @@ -1708,8 +1730,36 @@ { indexes[0] = mode; fBuffer->ResetColors(indexes, 1, true); + } + break; + // handle hyperlinks + case 8: + { + char* id = NULL; + char* start = (char*)params; + char* end = strpbrk(start, ";:"); + for (; end != NULL; start = end + 1) { + end = strpbrk(start, ";:"); + if (end == NULL) + break; + if (end - start > 3 && strncmp(start, "id=", 3) == 0) + id = strndup(start + 3, end - start + 3); + if (*end == ';') + break; } + if (end == NULL) + break; + BString uri(end + 1); + Attributes attributes = fBuffer->GetAttributes(); + if (uri.IsEmpty()) { + attributes.SetHyperlink(0); + } else { + attributes.SetHyperlink(fBuffer->PutHyperLink(id, uri)); + } + free(id); + fBuffer->SetAttributes(attributes); break; + } default: // printf("%d -> %s\n", mode, params); break; diff --git a/src/apps/terminal/TermView.cpp b/src/apps/terminal/TermView.cpp index 7b3666b..237e90d 100644 --- a/src/apps/terminal/TermView.cpp +++ b/src/apps/terminal/TermView.cpp @@ -452,7 +452,7 @@ } -inline int32 +int32 TermView::_LineAt(float y) const { int32 location = int32(y + fScrollOffset); @@ -1061,6 +1061,9 @@ TermView::_DrawLinePart(float x1, float y1, Attributes attr, char *buf, int32 width, Highlight* highlight, bool cursor, BView *inView) { + if (attr.IsHidden()) + return; + if (highlight != NULL) attr.state = highlight->Highlighter()->AdjustTextAttributes(attr.state); diff --git a/src/apps/terminal/TermView.h b/src/apps/terminal/TermView.h index a839215..8391818 100644 --- a/src/apps/terminal/TermView.h +++ b/src/apps/terminal/TermView.h @@ -188,7 +188,7 @@ private: // point and text offset conversion - inline int32 _LineAt(float y) const; + int32 _LineAt(float y) const; inline float _LineOffset(int32 index) const; TermPos _ConvertToTerminal(const BPoint& point) const; inline BPoint _ConvertFromTerminal(const TermPos& pos) const; diff --git a/src/apps/terminal/TermViewStates.cpp b/src/apps/terminal/TermViewStates.cpp index 1932b24..70a4866 100644 --- a/src/apps/terminal/TermViewStates.cpp +++ b/src/apps/terminal/TermViewStates.cpp @@ -833,10 +833,19 @@ HyperLink& _link, TermPos& _start, TermPos& _end) { TerminalBuffer* textBuffer = fView->fTextBuffer; + BasicTerminalBuffer* visibleTextBuffer = fView->fVisibleTextBuffer; BAutolock textBufferLocker(textBuffer); + int32 firstVisible = fView->_LineAt(0); TermPos pos = fView->_ConvertToTerminal(where); + UTF8Char character; + Attributes attr; + if (visibleTextBuffer->GetChar(pos.y - firstVisible, pos.x, character, attr) == A_CHAR + && attr.Hyperlink() != 0) { + return textBuffer->GetHyperLink(attr.Hyperlink(), _link); + } + // try to get a URL first BString text; if (!textBuffer->FindWord(pos, &fURLCharClassifier, false, _start, _end)) @@ -1120,6 +1129,7 @@ BLayoutBuilder::Menu<> menuBuilder(menu); switch (link.GetType()) { case HyperLink::TYPE_URL: + case HyperLink::TYPE_OSC_URL: menuBuilder .AddItem(B_TRANSLATE("Open link"), kMessageOpenLink) .AddItem(B_TRANSLATE("Copy link location"), kMessageCopyLink); @@ -1161,8 +1171,7 @@ case kMessageCopyAbsolutePath: { if (fLink.IsValid()) { - BString toCopy = message->what == kMessageCopyLink - ? fLink.Text() : fLink.Address(); + BString toCopy = fLink.Address(); if (!be_clipboard->Lock()) return true; diff --git a/src/apps/terminal/TerminalBuffer.cpp b/src/apps/terminal/TerminalBuffer.cpp index 3c51e26..976c087 100644 --- a/src/apps/terminal/TerminalBuffer.cpp +++ b/src/apps/terminal/TerminalBuffer.cpp @@ -34,7 +34,8 @@ fColorsPalette(NULL), fListenerValid(false), fMode(MODE_INTERPRET_META_KEY | MODE_META_KEY_SENDS_ESCAPE | MODE_CURSOR_BLINKING), - fCursorStyle(BLOCK_CURSOR) + fCursorStyle(BLOCK_CURSOR), + fNextOSCRef(1) { } @@ -44,6 +45,14 @@ free(fAlternateScreen); delete fAlternateHistory; delete[] fColorsPalette; + + HyperLinkRefMap::Iterator iterator = fHyperLinkForRef.GetIterator(); + while (iterator.HasNext()) { + HyperLinkRefMap::Entry entry = iterator.Next(); + delete entry.value; + } + fHyperLinkForID.Clear(); + fHyperLinkForRef.Clear(); } @@ -320,4 +329,34 @@ std::swap(fScreenOffset, fAlternateScreenOffset); std::swap(fAttributes, fAlternateAttributes); fAlternateScreenActive = !fAlternateScreenActive; +} + + +uint32 +TerminalBuffer::PutHyperLink(const char* id, BString& uri) +{ + HyperLink* hyperLink = NULL; + if (id != NULL) + hyperLink = fHyperLinkForID.Get(id); + if (hyperLink == NULL) { + hyperLink = new (std::nothrow) HyperLink(uri, fNextOSCRef++, id); + if (id != NULL) + fHyperLinkForID.Put(id, hyperLink); + HyperLink* oldHyperLink = fHyperLinkForRef.Get(hyperLink->OSCRef()); + fHyperLinkForRef.Put(hyperLink->OSCRef(), hyperLink); + delete oldHyperLink; + } + return hyperLink->OSCRef(); +} + + +bool +TerminalBuffer::GetHyperLink(uint32 ref, HyperLink &_link) +{ + HyperLink* hyperLink = fHyperLinkForRef.Get(ref); + if (hyperLink == NULL) { + return false; + } + _link = *hyperLink; + return true; } diff --git a/src/apps/terminal/TerminalBuffer.h b/src/apps/terminal/TerminalBuffer.h index 5a9009c..7f0118b 100644 --- a/src/apps/terminal/TerminalBuffer.h +++ b/src/apps/terminal/TerminalBuffer.h @@ -15,7 +15,11 @@ #include #include +#include +#include + #include "BasicTerminalBuffer.h" +#include "HyperLink.h" class TerminalBuffer : public BasicTerminalBuffer, public BLocker { @@ -57,6 +61,9 @@ void SetMode(uint32 mode); void ResetMode(uint32 mode); + uint32 PutHyperLink(const char* id, BString& uri); + bool GetHyperLink(uint32 ref, HyperLink& _link); + protected: virtual void NotifyListener(); @@ -78,6 +85,12 @@ uint32 fMode; int fCursorStyle; + + typedef HashMap, HyperLink*> HyperLinkRefMap; + HyperLinkRefMap fHyperLinkForRef; + typedef HashMap HyperLinkIDMap; + HyperLinkIDMap fHyperLinkForID; + uint32 fNextOSCRef; }; diff --git a/src/apps/terminal/TerminalLine.h b/src/apps/terminal/TerminalLine.h index 91bedbc..2c3357f 100644 --- a/src/apps/terminal/TerminalLine.h +++ b/src/apps/terminal/TerminalLine.h @@ -24,8 +24,10 @@ uint32 background; uint32 underline; int underlineStyle; + uint32 hyperlink; - Attributes() : state(0), foreground(0), background(0), underline(0), underlineStyle(0) {} + Attributes() : state(0), foreground(0), background(0), underline(0), underlineStyle(0), + hyperlink(0) {} inline void Reset() { @@ -41,6 +43,7 @@ inline bool IsUnder() const { return (state & UNDERLINE) == UNDERLINE; } inline bool IsInverse() const { return (state & INVERSE) == INVERSE; } inline bool IsOver() const { return (state & OVERLINE) == OVERLINE; } + inline bool IsHidden() const { return (state & HIDDEN) == HIDDEN; } inline bool IsMouse() const { return (state & MOUSE) == MOUSE; } inline bool IsForeSet() const { return (state & FORESET) == FORESET; } inline bool IsBackSet() const { return (state & BACKSET) == BACKSET; } @@ -96,6 +99,11 @@ state |= UNDERLINE; } + inline void SetHyperlink(uint32 id) + { + hyperlink = id; + } + inline void UnsetForeground() { state &= ~FORESET; @@ -157,6 +165,12 @@ UnderlineStyle() const { return underlineStyle; + } + + inline uint32 + Hyperlink() const + { + return hyperlink; } inline Attributes& @@ -178,7 +192,8 @@ && foreground == other.foreground && background == other.background && underline == other.underline - && underlineStyle == other.underlineStyle; + && underlineStyle == other.underlineStyle + && hyperlink == other.hyperlink; } inline bool @@ -188,7 +203,8 @@ || foreground != other.foreground || background != other.background || underline != other.underline - || underlineStyle != other.underlineStyle; + || underlineStyle != other.underlineStyle + || hyperlink != other.hyperlink; } }; @@ -203,7 +219,8 @@ return (attributes.state & CHAR_ATTRIBUTES) != (other.state & CHAR_ATTRIBUTES) || attributes.foreground != other.foreground - || attributes.background != other.background; + || attributes.background != other.background + || attributes.hyperlink != other.hyperlink; } }; diff --git a/src/apps/terminal/VTPrsTbl.c b/src/apps/terminal/VTPrsTbl.c index c798d4b..eb2bb00 100644 --- a/src/apps/terminal/VTPrsTbl.c +++ b/src/apps/terminal/VTPrsTbl.c @@ -1038,9 +1038,9 @@ CASE_IGNORE, /* SP ! " # */ CASE_CSI_SP, +CASE_CSI_EXCL, CASE_ESC_IGNORE, CASE_ESC_IGNORE, -CASE_ESC_IGNORE, /* $ % & ' */ CASE_ESC_IGNORE, CASE_ESC_IGNORE, @@ -1137,7 +1137,7 @@ CASE_CPR, CASE_GROUND_STATE, /* p q r s */ -CASE_GROUND_STATE, +CASE_DECSTR, CASE_DECSCUSR_ETC, CASE_DECSTBM, CASE_GROUND_STATE, diff --git a/src/apps/terminal/VTparse.h b/src/apps/terminal/VTparse.h index 2d91d49..c9772a1 100644 --- a/src/apps/terminal/VTparse.h +++ b/src/apps/terminal/VTparse.h @@ -104,4 +104,6 @@ #define CASE_REP 101 #define CASE_DEC_DOL 102 #define CASE_DECRQM 103 +#define CASE_CSI_EXCL 104 +#define CASE_DECSTR 105 -- gitore 0.2.3