Skip to content

Commit

Permalink
make links displayed in terminal clickable (rstudio#7846)
Browse files Browse the repository at this point in the history
- Fixes rstudio#6621
- Was supported in RStudio 1.1 and 1.2, but was lost in RStudio 1.3 because xterm.js moved it out of core library into a separate add-on
- Also added a user preference for enabling/disabling this feature
  • Loading branch information
gtritchie authored Sep 24, 2020
1 parent f5ed94c commit 7fbdb81
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 7 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,5 @@
* Fixed issue where Job Launcher admin users would have `gid=0` in Slurm Launcher Sessions (Pro #1935)
* Fixed issue causing script errors when reloading Shiny applications from the editor toolbar (#7762)
* Fixed issue causing C++ diagnostics to fail when Xcode developer tools were active (#7824)
* Added option for clickable links in Terminal pane (#6621)

7 changes: 7 additions & 0 deletions src/cpp/session/include/session/prefs/UserPrefValues.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ namespace prefs {
#define kTerminalRenderer "terminal_renderer"
#define kTerminalRendererCanvas "canvas"
#define kTerminalRendererDom "dom"
#define kTerminalWeblinks "terminal_weblinks"
#define kShowRmdRenderCommand "show_rmd_render_command"
#define kEnableTextDrag "enable_text_drag"
#define kShowHiddenFiles "show_hidden_files"
Expand Down Expand Up @@ -1188,6 +1189,12 @@ class UserPrefValues: public Preferences
std::string terminalRenderer();
core::Error setTerminalRenderer(std::string val);

/**
* Whether web links displayed in the Terminal tab are made clickable.
*/
bool terminalWeblinks();
core::Error setTerminalWeblinks(bool val);

/**
* Whether to print the render command use to knit R Markdown documents in the R Markdown tab.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/cpp/session/prefs/UserPrefValues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,19 @@ core::Error UserPrefValues::setTerminalRenderer(std::string val)
return writePref("terminal_renderer", val);
}

/**
* Whether web links displayed in the Terminal tab are made clickable.
*/
bool UserPrefValues::terminalWeblinks()
{
return readPref<bool>("terminal_weblinks");
}

core::Error UserPrefValues::setTerminalWeblinks(bool val)
{
return writePref("terminal_weblinks", val);
}

/**
* Whether to print the render command use to knit R Markdown documents in the R Markdown tab.
*/
Expand Down Expand Up @@ -2878,6 +2891,7 @@ std::vector<std::string> UserPrefValues::allKeys()
kTerminalTrackEnvironment,
kTerminalBellStyle,
kTerminalRenderer,
kTerminalWeblinks,
kShowRmdRenderCommand,
kEnableTextDrag,
kShowHiddenFiles,
Expand Down
8 changes: 7 additions & 1 deletion src/cpp/session/resources/schema/user-prefs-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,13 @@
"title": "Terminal tab rendering engine",
"description": "Terminal rendering engine: canvas is faster, dom may be needed for some browsers or graphics cards"
},
"show_rmd_render_command": {
"terminal_weblinks": {
"type": "boolean",
"default": true,
"title": "Make links in Terminal clickable",
"description": "Whether web links displayed in the Terminal tab are made clickable."
},
"show_rmd_render_command": {
"type": "boolean",
"default": false,
"title": "Show R Markdown render command",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1922,6 +1922,18 @@ public PrefValue<String> terminalRenderer()
public final static String TERMINAL_RENDERER_CANVAS = "canvas";
public final static String TERMINAL_RENDERER_DOM = "dom";

/**
* Whether web links displayed in the Terminal tab are made clickable.
*/
public PrefValue<Boolean> terminalWeblinks()
{
return bool(
"terminal_weblinks",
"Make links in Terminal clickable",
"Whether web links displayed in the Terminal tab are made clickable.",
true);
}

/**
* Whether to print the render command use to knit R Markdown documents in the R Markdown tab.
*/
Expand Down Expand Up @@ -3256,6 +3268,8 @@ public void syncPrefs(String layer, JsObject source)
terminalBellStyle().setValue(layer, source.getString("terminal_bell_style"));
if (source.hasKey("terminal_renderer"))
terminalRenderer().setValue(layer, source.getString("terminal_renderer"));
if (source.hasKey("terminal_weblinks"))
terminalWeblinks().setValue(layer, source.getBool("terminal_weblinks"));
if (source.hasKey("show_rmd_render_command"))
showRmdRenderCommand().setValue(layer, source.getBool("show_rmd_render_command"));
if (source.hasKey("enable_text_drag"))
Expand Down Expand Up @@ -3543,6 +3557,7 @@ public List<PrefValue<?>> allPrefs()
prefs.add(terminalTrackEnvironment());
prefs.add(terminalBellStyle());
prefs.add(terminalRenderer());
prefs.add(terminalWeblinks());
prefs.add(showRmdRenderCommand());
prefs.add(enableTextDrag());
prefs.add(showHiddenFiles());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ public void execute()
chkHardwareAcceleration_ = new CheckBox("Hardware acceleration");
general.add(lessSpaced(chkHardwareAcceleration_));
chkAudibleBell_ = new CheckBox("Audible bell");
general.add(chkAudibleBell_);
general.add(lessSpaced(chkAudibleBell_));
chkWebLinks_ = new CheckBox("Clickable web links");
general.add(chkWebLinks_);

HelpLink helpLink = new HelpLink("Using the RStudio terminal", "rstudio_terminal", false);
nudgeRight(helpLink);
Expand Down Expand Up @@ -338,6 +340,7 @@ public void onError(ServerError error) { }
}

chkAudibleBell_.setValue(prefs_.terminalBellStyle().getValue() == UserPrefsAccessor.TERMINAL_BELL_STYLE_SOUND);
chkWebLinks_.setValue(prefs_.terminalWeblinks().getValue());
chkHardwareAcceleration_.setValue(prefs_.terminalRenderer().getValue() == UserPrefsAccessor.TERMINAL_RENDERER_CANVAS);

if (!initialDirectory_.setValue(prefs.terminalInitialDirectory().getValue()))
Expand Down Expand Up @@ -370,6 +373,7 @@ public RestartRequirement onApply(UserPrefs rPrefs)
UserPrefsAccessor.TERMINAL_BELL_STYLE_SOUND : UserPrefsAccessor.TERMINAL_BELL_STYLE_NONE);
prefs_.terminalRenderer().setGlobalValue(chkHardwareAcceleration_.getValue() ?
UserPrefsAccessor.TERMINAL_RENDERER_CANVAS : UserPrefsAccessor.TERMINAL_RENDERER_DOM);
prefs_.terminalWeblinks().setGlobalValue(chkWebLinks_.getValue());

prefs_.terminalInitialDirectory().setGlobalValue(initialDirectory_.getValue());
prefs_.terminalCloseBehavior().setGlobalValue(autoClosePref_.getValue());
Expand Down Expand Up @@ -451,6 +455,7 @@ private void addTextBoxChooser(Panel panel, String textWidth, FormLabel captionL

private final CheckBox chkHardwareAcceleration_;
private final CheckBox chkAudibleBell_;
private final CheckBox chkWebLinks_;

private SelectWidget autoClosePref_;
private SelectWidget busyMode_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ public void addNewTerminalPanel(
ConsoleProcessInfo procInfo,
XTermOptions options,
boolean tabMovesFocus,
boolean showWebLinks,
boolean createdByApi,
CommandWithArg<TerminalSession> callback)
{
TerminalSession session = new TerminalSession(procInfo, options, tabMovesFocus, createdByApi);
TerminalSession session = new TerminalSession(procInfo, options, tabMovesFocus, showWebLinks, createdByApi);
add(session);
showWidget(session);
Scheduler.get().scheduleDeferred(() -> callback.execute(session));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ public void createTerminal(String postCreateText, String initialDirectory)
initialDirectory == null ? "" : initialDirectory);
terminalSessionsPanel_.addNewTerminalPanel(info, defaultTerminalOptions(),
uiPrefs_.tabKeyMoveFocus().getValue(),
uiPrefs_.terminalWeblinks().getValue(),
false /*createdByApi*/, session ->
{
terminals_.startTerminal(session, new ResultCallback<Boolean, String>()
Expand Down Expand Up @@ -875,6 +876,7 @@ public void onFailure(String msg)

terminalSessionsPanel_.addNewTerminalPanel(existing, defaultTerminalOptions(),
uiPrefs_.tabKeyMoveFocus().getValue(),
uiPrefs_.terminalWeblinks().getValue(),
event.createdByApi(), session ->
{
terminals_.startTerminal(session, new ResultCallback<Boolean, String>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ public class TerminalSession extends XTermWidget
* @param info terminal metadata
* @param options terminal emulator options
* @param tabMovesFocus does pressing tab key move focus out of terminal
* @param showWebLinks links detected and made clickable
* @param createdByApi was this terminal just created by the rstudioapi
*/
public TerminalSession(ConsoleProcessInfo info,
XTermOptions options,
boolean tabMovesFocus,
boolean showWebLinks,
boolean createdByApi)
{
super(options, tabMovesFocus);
super(options, tabMovesFocus, showWebLinks);

RStudioGinjector.INSTANCE.injectMembers(this);
procInfo_ = info;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ public interface XTermResources extends ClientBundle

@Source("fit.js")
StaticDataResource xtermfitjs();

@Source("web-links.js")
StaticDataResource xtermweblinksjs();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* XTermWebLinksAddon.java
*
* Copyright (C) 2020 by RStudio, PBC
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.workbench.views.terminal.xterm;

import jsinterop.annotations.JsType;

/**
* An xterm.js addon that enables web links.
* https://github.com/xtermjs/xterm.js/blob/4.7.0/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts
*/
@JsType(isNative = true, namespace = "WebLinksAddon", name = "WebLinksAddon")
public class XTermWebLinksAddon extends XTermAddon
{
/*
* NOTE: only supporting the no-argument default constructor which makes URLs into clickable
* links that launch external browser. Additional features are available in the add-on for
* registering custom providers and callback.
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ public class XTermWidget extends Widget
/**
* Creates an XTermWidget.
*/
public XTermWidget(XTermOptions options, boolean tabMovesFocus)
public XTermWidget(XTermOptions options, boolean tabMovesFocus, boolean showWebLinks)
{
options_ = options;
tabMovesFocus_ = tabMovesFocus;
showWebLinks_ = showWebLinks;

// Create an element to hold the terminal widget
setElement(Document.get().createDivElement());
Expand All @@ -94,6 +95,11 @@ public void open(Operation callback)
terminal_ = new XTermTerminal(options_);
fit_ = new XTermFitAddon();
terminal_.loadAddon(fit_);
if (showWebLinks_)
{
webLinks_ = new XTermWebLinksAddon();
terminal_.loadAddon(webLinks_);
}
terminal_.open(getElement());
terminal_.focus();
terminal_.addClass("ace_editor");
Expand Down Expand Up @@ -449,8 +455,11 @@ public static void load(final Command command)
{
xtermFitLoader_.addCallback(() ->
{
if (command != null)
command.execute();
xtermWebLinksLoader_.addCallback(() ->
{
if (command != null)
command.execute();
});
});
});
});
Expand All @@ -477,11 +486,16 @@ public void refresh()
private static final ExternalJavaScriptLoader xtermFitLoader_ =
new ExternalJavaScriptLoader(XTermResources.INSTANCE.xtermfitjs().getSafeUri().asString());

private static final ExternalJavaScriptLoader xtermWebLinksLoader_ =
new ExternalJavaScriptLoader(XTermResources.INSTANCE.xtermweblinksjs().getSafeUri().asString());

private XTermTerminal terminal_;
private XTermFitAddon fit_;
private XTermWebLinksAddon webLinks_;
private boolean initialized_ = false;
private final XTermOptions options_;
private boolean tabMovesFocus_;
private boolean showWebLinks_;
private final ArrayList<XTermDisposable> xtermEventUnsubscribe_ = new ArrayList<>();

private final static String XTERM_CLASS = "xterm-rstudio";
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/gwt/tools/build-xterm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mkdir xterm.js
cd xterm.js
npm install xterm@4.9.0
npm install xterm-addon-fit@0.4.0
npm install xterm-addon-web-links@0.4.0

XTERM_TARGET_DIR=../../src/org/rstudio/studio/client/workbench/views/terminal/xterm

Expand All @@ -20,5 +21,6 @@ cp ./node_modules/xterm/css/xterm.css ${XTERM_TARGET_DIR}/xterm.css
# Strip source-map references since they don't work via ClientBundle
sed '/^\/\/# sourceMappingURL=/d' ./node_modules/xterm/lib/xterm.js > ${XTERM_TARGET_DIR}/xterm.js
sed '/^\/\/# sourceMappingURL=/d' ./node_modules/xterm-addon-fit/lib/xterm-addon-fit.js > ${XTERM_TARGET_DIR}/fit.js
sed '/^\/\/# sourceMappingURL=/d' ./node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js > ${XTERM_TARGET_DIR}/web-links.js

echo Done!

0 comments on commit 7fbdb81

Please sign in to comment.