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 <pulkomandy@pulkomandy.tk>
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Diff
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(-)
@@ -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)
{
}
@@ -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;
};
@@ -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)
@@ -646,6 +646,12 @@
param[nparam++] = ' ';
break;
case CASE_CSI_EXCL:
if (nparam < NPARAM)
param[nparam++] = '!';
break;
case CASE_DEC_DOL:
if (nparam < NPARAM)
@@ -836,6 +842,10 @@
case 7:
attributes |= INVERSE;
break;
case 8:
attributes |= HIDDEN;
break;
case 21:
@@ -852,6 +862,10 @@
case 27:
attributes &= ~INVERSE;
break;
case 28:
attributes &= ~HIDDEN;
break;
case 53:
@@ -1294,6 +1308,14 @@
case CASE_DECRQM:
if (nparam == 2 && param[1] == '$') {
_DecPrivateModeRequest(param[0]);
}
parsestate = groundtable;
break;
case CASE_DECSTR:
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;
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:
break;
@@ -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);
@@ -188,7 +188,7 @@
private:
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;
@@ -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);
}
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;
@@ -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;
}
@@ -15,7 +15,11 @@
#include <Locker.h>
#include <Messenger.h>
#include <HashMap.h>
#include <HashString.h>
#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<HashKey32<int32>, HyperLink*> HyperLinkRefMap;
HyperLinkRefMap fHyperLinkForRef;
typedef HashMap<HashString, HyperLink*> HyperLinkIDMap;
HyperLinkIDMap fHyperLinkForID;
uint32 fNextOSCRef;
};
@@ -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;
}
};
@@ -1038,9 +1038,9 @@
CASE_IGNORE,
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,
CASE_GROUND_STATE,
CASE_DECSTR,
CASE_DECSCUSR_ETC,
CASE_DECSTBM,
CASE_GROUND_STATE,
@@ -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