@@ -183,6 +183,10 @@ type View struct {
183
183
// if true, the user can scroll all the way past the last item until it appears at the top of the view
184
184
CanScrollPastBottom bool
185
185
186
+ // if true, the view will automatically recognize https: URLs in the content written to it and render
187
+ // them as hyperlinks
188
+ AutoRenderHyperLinks bool
189
+
186
190
// if true, the view will underline hyperlinks only when the cursor is on
187
191
// them; otherwise, they will always be underlined
188
192
UnderlineHyperLinksOnlyOnHover bool
@@ -780,6 +784,7 @@ func (v *View) writeRunes(p []rune) {
780
784
for _ , r := range p {
781
785
switch r {
782
786
case '\n' :
787
+ v .autoRenderHyperlinksInCurrentLine ()
783
788
if c , ok := v .readCell (v .wx + 1 , v .wy ); ! ok || c .chr == 0 {
784
789
v .writeCells (v .wx , v .wy , []cell {{
785
790
chr : 0 ,
@@ -793,6 +798,7 @@ func (v *View) writeRunes(p []rune) {
793
798
v .lines = append (v .lines , nil )
794
799
}
795
800
case '\r' :
801
+ v .autoRenderHyperlinksInCurrentLine ()
796
802
if c , ok := v .readCell (v .wx , v .wy ); ! ok || c .chr == 0 {
797
803
v .writeCells (v .wx , v .wy , []cell {{
798
804
chr : 0 ,
@@ -829,6 +835,61 @@ func (v *View) writeString(s string) {
829
835
v .writeRunes ([]rune (s ))
830
836
}
831
837
838
+ func findSubstring (line []cell , substringToFind []rune ) int {
839
+ for i := 0 ; i < len (line )- len (substringToFind ); i ++ {
840
+ for j := 0 ; j < len (substringToFind ); j ++ {
841
+ if line [i + j ].chr != substringToFind [j ] {
842
+ break
843
+ }
844
+ if j == len (substringToFind )- 1 {
845
+ return i
846
+ }
847
+ }
848
+ }
849
+ return - 1
850
+ }
851
+
852
+ func (v * View ) autoRenderHyperlinksInCurrentLine () {
853
+ if ! v .AutoRenderHyperLinks {
854
+ return
855
+ }
856
+
857
+ // We need a heuristic to find the end of a hyperlink. Searching for the
858
+ // first character that is not a valid URI character is not quite good
859
+ // enough, because in markdown it's common to have a hyperlink followed by a
860
+ // ')', so we want to stop there. Hopefully URLs containing ')' are uncommon
861
+ // enough that this is not a problem.
862
+ lineEndCharacters := map [rune ]bool {
863
+ '\000' : true ,
864
+ ' ' : true ,
865
+ '\n' : true ,
866
+ '>' : true ,
867
+ '"' : true ,
868
+ ')' : true ,
869
+ }
870
+ line := v .lines [v .wy ]
871
+ start := 0
872
+ for {
873
+ linkStart := findSubstring (line [start :], []rune ("https://" ))
874
+ if linkStart == - 1 {
875
+ break
876
+ }
877
+ linkStart += start
878
+ link := ""
879
+ linkEnd := linkStart
880
+ for ; linkEnd < len (line ); linkEnd ++ {
881
+ if _ , ok := lineEndCharacters [line [linkEnd ].chr ]; ok {
882
+ break
883
+ }
884
+ link += string (line [linkEnd ].chr )
885
+ }
886
+ for i := linkStart ; i < linkEnd ; i ++ {
887
+ v.lines [v.wy ][i ].hyperlink = link
888
+ }
889
+ start = linkEnd
890
+ }
891
+ }
892
+
832
893
// parseInput parses char by char the input written to the View. It returns nil
833
894
// while processing ESC sequences. Otherwise, it returns a cell slice that
834
895
// contains the processed data.
0 commit comments