Constructing a text editor with custom styling for urls

Let's build an editor starting with a string like the following one:

string := 'At https://gtoolkit.com you can find more details about Glamorous Toolkit.'.
  

A basic editor is an instance of BrEditor BrEditorElement subclass: #BrEditor uses: TBrLayoutResizable + TBrScrollable instanceVariableNames: 'textReplacedHandler lineCount' classVariableNames: '' package: 'Brick-Editor - UI'

editor := BrEditor new aptitude: BrGlamorousCodeEditorAptitude.
editor text: string.
editor
  

But what about a more elaborate editing experience? For example, what if we want to have an editor that reacts to links as we type them?

The first thing we need is a way to detect the links. For our exercise, we will use a sea parser that extracts a collection with the ranges of the links:

urlParser := ('https://' asPParser token
		, String space asPParser negate star token)
		==> [ :t | t first start -> t last stop ].
parser := (urlParser sea ==> #second) star optimize.
  

Let's try it quickly:

parser parse: string
  

Now that we have a way to detect the ranges of urls, we can create a custom styler to highlight them:

styler := BlPluggableStyler new
		block: [ :text | 
			(parser parse: text asString)
				do: [ :each | 
					(text from: each key to: each value)
						underlineDo: [:d | d color: Color blue ];
						foreground: Color blue ] ].
editor := BrEditor new aptitude: BrGlamorousCodeEditorAptitude.
editor styler: styler.
editor text: string.
editor
  

In this case we use a BlPluggableStyler BlTextStyler subclass: #BlPluggableStyler instanceVariableNames: 'block' classVariableNames: '' package: 'Bloc-Text-Text-Styler' that we can script through a block that takes the text as argument. In that block, we use the parser and for each detected range we add TBlTextStyleable>>#underlineDo: underlineDo: aBlock self decorationDo: [ :aBlTextDecorationAttribute | aBlock value: aBlTextDecorationAttribute underline ] and TBlTextStyleable>>#foreground: foreground: aBlPaint self attributesBuilder attribute: (BlTextForegroundAttribute paint: aBlPaint) .

Highlighting links is nice, but what about seeing the webpage? One option would be to handle clicking. This can be achieved through TBlTextStyleable>>#onClick: onClick: aClickAction "Perform actions when a user clicks on a piece of text. The click action is a block in the following form: [ :aTBrTextEditorTextualPiece :aWorldElement :anEditorElement :anEvent | ]" self attributesBuilder attribute: (BrTextClickAttribute new action: aClickAction) . Another option is to insert an expandable web browser right in the editor. That can be done through a so called adornment. Let's try both of them:

styler := BlPluggableStyler new
		block: [ :text | 
			(parser parse: text asString)
				do: [ :each | 
					| urlText |
					urlText := text from: each key to: each value.
					urlText
						foreground: Color blue;
						onClick: [ :aTextualPiece :aTarget :aTextEditor :anEvent | aTarget phlow spawnObject: (GtWebViewElement new url: urlText asString) ];
						attribute: (BrTextHoverStylableAttribute new
								attribute: (BlTextDecorationAttribute new underline color: Color blue));
						expandingAdornment: [ GtWebViewElement new
								url: urlText asString;
								height: 400;
								background: Color white;
								addAptitude: BrShadowAptitude ] ] ].
editor := BrEditor new aptitude: BrGlamorousCodeEditorAptitude.
editor styler: styler.
editor text: string.
editor