⛏️ index : haiku.git

author Jérôme Duval <jerome.duval@gmail.com> 2025-10-01 17:47:45.0 +02:00:00
committer Adrien Destugues <pulkomandy@pulkomandy.tk> 2025-12-13 21:25:16.0 +00:00:00
commit
1a163d0ce9e723e9011dc3cd875fbff05bd72387 [patch]
tree
1e718270639227c6e099bffc5529e5616c5bc1c3
parent
cfd2df0eba8af48635b0dd7409d8d134aead64d6
download
1a163d0ce9e723e9011dc3cd875fbff05bd72387.tar.gz

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(-)

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 <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;
};


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