Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
8e6a4118
V
vscode
项目概览
xxadev
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
8e6a4118
编写于
3月 13, 2020
作者:
D
Daniel Imms
提交者:
GitHub
3月 13, 2020
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #92613 from microsoft/tyriar/link_handlers
Implement terminal link handler API
上级
8c4c01fb
d59c6186
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
281 addition
and
49 deletion
+281
-49
src/vs/vscode.proposed.d.ts
src/vs/vscode.proposed.d.ts
+16
-0
src/vs/workbench/api/browser/mainThreadTerminalService.ts
src/vs/workbench/api/browser/mainThreadTerminalService.ts
+19
-2
src/vs/workbench/api/common/extHost.api.impl.ts
src/vs/workbench/api/common/extHost.api.impl.ts
+4
-0
src/vs/workbench/api/common/extHost.protocol.ts
src/vs/workbench/api/common/extHost.protocol.ts
+3
-0
src/vs/workbench/api/common/extHostTerminalService.ts
src/vs/workbench/api/common/extHostTerminalService.ts
+37
-2
src/vs/workbench/contrib/terminal/browser/terminal.ts
src/vs/workbench/contrib/terminal/browser/terminal.ts
+25
-0
src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
...vs/workbench/contrib/terminal/browser/terminalInstance.ts
+7
-1
src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts
...workbench/contrib/terminal/browser/terminalLinkHandler.ts
+76
-35
src/vs/workbench/contrib/terminal/browser/terminalService.ts
src/vs/workbench/contrib/terminal/browser/terminalService.ts
+47
-1
src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts
...contrib/terminal/test/browser/terminalLinkHandler.test.ts
+47
-8
未找到文件。
src/vs/vscode.proposed.d.ts
浏览文件 @
8e6a4118
...
...
@@ -1086,6 +1086,22 @@ declare module 'vscode' {
//#endregion
//#region Terminal link handlers https://github.com/microsoft/vscode/issues/91606
export
namespace
window
{
export
function
registerTerminalLinkHandler
(
handler
:
TerminalLinkHandler
):
Disposable
;
}
export
interface
TerminalLinkHandler
{
/**
* @return true when the link was handled (and should not be considered by
* other providers including the default), false when the link was not handled.
*/
handleLink
(
terminal
:
Terminal
,
link
:
string
):
ProviderResult
<
boolean
>
;
}
//#endregion
//#region Joh -> exclusive document filters
export
interface
DocumentFilter
{
...
...
src/vs/workbench/api/browser/mainThreadTerminalService.ts
浏览文件 @
8e6a4118
...
...
@@ -3,13 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
DisposableStore
,
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
DisposableStore
,
Disposable
,
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
IShellLaunchConfig
,
ITerminalProcessExtHostProxy
,
ISpawnExtHostProcessRequest
,
ITerminalDimensions
,
EXT_HOST_CREATION_DELAY
,
IAvailableShellsRequest
,
IDefaultShellAndArgsRequest
,
IStartExtensionTerminalRequest
}
from
'
vs/workbench/contrib/terminal/common/terminal
'
;
import
{
ExtHostContext
,
ExtHostTerminalServiceShape
,
MainThreadTerminalServiceShape
,
MainContext
,
IExtHostContext
,
IShellLaunchConfigDto
,
TerminalLaunchConfig
,
ITerminalDimensionsDto
}
from
'
vs/workbench/api/common/extHost.protocol
'
;
import
{
extHostNamedCustomer
}
from
'
vs/workbench/api/common/extHostCustomers
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
StopWatch
}
from
'
vs/base/common/stopwatch
'
;
import
{
ITerminalInstanceService
,
ITerminalService
,
ITerminalInstance
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
ITerminalInstanceService
,
ITerminalService
,
ITerminalInstance
,
ITerminalBeforeHandleLinkEvent
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
IRemoteAgentService
}
from
'
vs/workbench/services/remote/common/remoteAgentService
'
;
import
{
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
TerminalDataBufferer
}
from
'
vs/workbench/contrib/terminal/common/terminalDataBuffering
'
;
...
...
@@ -23,6 +23,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private
readonly
_terminalProcesses
=
new
Map
<
number
,
Promise
<
ITerminalProcessExtHostProxy
>>
();
private
readonly
_terminalProcessesReady
=
new
Map
<
number
,
(
proxy
:
ITerminalProcessExtHostProxy
)
=>
void
>
();
private
_dataEventTracker
:
TerminalDataEventTracker
|
undefined
;
private
_linkHandler
:
IDisposable
|
undefined
;
constructor
(
extHostContext
:
IExtHostContext
,
...
...
@@ -146,6 +147,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
}
public
$startHandlingLinks
():
void
{
this
.
_linkHandler
?.
dispose
();
this
.
_linkHandler
=
this
.
_terminalService
.
addLinkHandler
(
this
.
_remoteAuthority
||
''
,
e
=>
this
.
_handleLink
(
e
));
}
public
$stopHandlingLinks
():
void
{
this
.
_linkHandler
?.
dispose
();
}
private
async
_handleLink
(
e
:
ITerminalBeforeHandleLinkEvent
):
Promise
<
boolean
>
{
if
(
!
e
.
terminal
)
{
return
false
;
}
return
this
.
_proxy
.
$handleLink
(
e
.
terminal
.
id
,
e
.
link
);
}
private
_onActiveTerminalChanged
(
terminalId
:
number
|
null
):
void
{
this
.
_proxy
.
$acceptActiveTerminalChanged
(
terminalId
);
}
...
...
src/vs/workbench/api/common/extHost.api.impl.ts
浏览文件 @
8e6a4118
...
...
@@ -564,6 +564,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
return
extHostTerminalService
.
createTerminal
(
nameOrOptions
,
shellPath
,
shellArgs
);
},
registerTerminalLinkHandler
(
handler
:
vscode
.
TerminalLinkHandler
):
vscode
.
Disposable
{
checkProposedApiEnabled
(
extension
);
return
extHostTerminalService
.
registerLinkHandler
(
handler
);
},
registerTreeDataProvider
(
viewId
:
string
,
treeDataProvider
:
vscode
.
TreeDataProvider
<
any
>
):
vscode
.
Disposable
{
return
extHostTreeViews
.
registerTreeDataProvider
(
viewId
,
treeDataProvider
,
extension
);
},
...
...
src/vs/workbench/api/common/extHost.protocol.ts
浏览文件 @
8e6a4118
...
...
@@ -432,6 +432,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$show
(
terminalId
:
number
,
preserveFocus
:
boolean
):
void
;
$startSendingDataEvents
():
void
;
$stopSendingDataEvents
():
void
;
$startHandlingLinks
():
void
;
$stopHandlingLinks
():
void
;
// Process
$sendProcessTitle
(
terminalId
:
number
,
title
:
string
):
void
;
...
...
@@ -1311,6 +1313,7 @@ export interface ExtHostTerminalServiceShape {
$acceptWorkspacePermissionsChanged
(
isAllowed
:
boolean
):
void
;
$getAvailableShells
():
Promise
<
IShellDefinitionDto
[]
>
;
$getDefaultShellAndArgs
(
useAutomationShell
:
boolean
):
Promise
<
IShellAndArgsDto
>
;
$handleLink
(
id
:
number
,
link
:
string
):
Promise
<
boolean
>
;
}
export
interface
ExtHostSCMShape
{
...
...
src/vs/workbench/api/common/extHostTerminalService.ts
浏览文件 @
8e6a4118
...
...
@@ -14,6 +14,7 @@ import { timeout } from 'vs/base/common/async';
import
{
IExtHostRpcService
}
from
'
vs/workbench/api/common/extHostRpcService
'
;
import
{
TerminalDataBufferer
}
from
'
vs/workbench/contrib/terminal/common/terminalDataBuffering
'
;
import
{
IDisposable
,
DisposableStore
}
from
'
vs/base/common/lifecycle
'
;
import
{
Disposable
as
VSCodeDisposable
}
from
'
./extHostTypes
'
;
export
interface
IExtHostTerminalService
extends
ExtHostTerminalServiceShape
{
...
...
@@ -34,6 +35,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape {
attachPtyToTerminal
(
id
:
number
,
pty
:
vscode
.
Pseudoterminal
):
void
;
getDefaultShell
(
useAutomationShell
:
boolean
,
configProvider
:
ExtHostConfigProvider
):
string
;
getDefaultShellArgs
(
useAutomationShell
:
boolean
,
configProvider
:
ExtHostConfigProvider
):
string
[]
|
string
;
registerLinkHandler
(
handler
:
vscode
.
TerminalLinkHandler
):
vscode
.
Disposable
}
export
const
IExtHostTerminalService
=
createDecorator
<
IExtHostTerminalService
>
(
'
IExtHostTerminalService
'
);
...
...
@@ -295,6 +297,9 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
protected
_extensionTerminalAwaitingStart
:
{
[
id
:
number
]:
{
initialDimensions
:
ITerminalDimensionsDto
|
undefined
}
|
undefined
}
=
{};
protected
_getTerminalPromises
:
{
[
id
:
number
]:
Promise
<
ExtHostTerminal
>
}
=
{};
private
readonly
_bufferer
:
TerminalDataBufferer
;
private
readonly
_linkHandlers
:
Set
<
vscode
.
TerminalLinkHandler
>
=
new
Set
();
public
get
activeTerminal
():
ExtHostTerminal
|
undefined
{
return
this
.
_activeTerminal
;
}
public
get
terminals
():
ExtHostTerminal
[]
{
return
this
.
_terminals
;
}
...
...
@@ -309,8 +314,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
protected
readonly
_onDidWriteTerminalData
:
Emitter
<
vscode
.
TerminalDataWriteEvent
>
;
public
get
onDidWriteTerminalData
():
Event
<
vscode
.
TerminalDataWriteEvent
>
{
return
this
.
_onDidWriteTerminalData
&&
this
.
_onDidWriteTerminalData
.
event
;
}
private
readonly
_bufferer
:
TerminalDataBufferer
;
constructor
(
@
IExtHostRpcService
extHostRpc
:
IExtHostRpcService
)
{
...
...
@@ -535,6 +538,38 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
return
id
;
}
public
registerLinkHandler
(
handler
:
vscode
.
TerminalLinkHandler
):
vscode
.
Disposable
{
this
.
_linkHandlers
.
add
(
handler
);
if
(
this
.
_linkHandlers
.
size
===
1
)
{
this
.
_proxy
.
$startHandlingLinks
();
}
return
new
VSCodeDisposable
(()
=>
{
this
.
_linkHandlers
.
delete
(
handler
);
if
(
this
.
_linkHandlers
.
size
===
0
)
{
this
.
_proxy
.
$stopHandlingLinks
();
}
});
}
public
async
$handleLink
(
id
:
number
,
link
:
string
):
Promise
<
boolean
>
{
const
terminal
=
this
.
_getTerminalById
(
id
);
if
(
!
terminal
)
{
return
false
;
}
// Call each handler synchronously so multiple handlers aren't triggered at once
const
it
=
this
.
_linkHandlers
.
values
();
let
next
=
it
.
next
();
while
(
!
next
.
done
)
{
const
handled
=
await
next
.
value
.
handleLink
(
terminal
,
link
);
if
(
handled
)
{
return
true
;
}
next
=
it
.
next
();
}
return
false
;
}
private
_onProcessExit
(
id
:
number
,
exitCode
:
number
|
undefined
):
void
{
this
.
_bufferer
.
stopBuffering
(
id
);
...
...
src/vs/workbench/contrib/terminal/browser/terminal.ts
浏览文件 @
8e6a4118
...
...
@@ -131,6 +131,14 @@ export interface ITerminalService {
findNext
():
void
;
findPrevious
():
void
;
/**
* Link handlers can be registered here to allow intercepting links clicked in the terminal.
* When a link is clicked, the link will be considered handled when the first interceptor
* resolves with true. It will be considered not handled when _all_ link handlers resolve with
* false, or 3 seconds have elapsed.
*/
addLinkHandler
(
key
:
string
,
callback
:
TerminalLinkHandlerCallback
):
IDisposable
;
selectDefaultWindowsShell
():
Promise
<
void
>
;
setContainers
(
panelContainer
:
HTMLElement
,
terminalContainer
:
HTMLElement
):
void
;
...
...
@@ -179,6 +187,18 @@ export enum WindowsShellType {
}
export
type
TerminalShellType
=
WindowsShellType
|
undefined
;
export
const
LINK_INTERCEPT_THRESHOLD
=
3000
;
export
interface
ITerminalBeforeHandleLinkEvent
{
terminal
?:
ITerminalInstance
;
/** The text of the link */
link
:
string
;
/** Call with whether the link was handled by the interceptor */
resolve
(
wasHandled
:
boolean
):
void
;
}
export
type
TerminalLinkHandlerCallback
=
(
e
:
ITerminalBeforeHandleLinkEvent
)
=>
Promise
<
boolean
>
;
export
interface
ITerminalInstance
{
/**
* The ID of the terminal instance, this is an arbitrary number only used to identify the
...
...
@@ -240,6 +260,11 @@ export interface ITerminalInstance {
*/
onExit
:
Event
<
number
|
undefined
>
;
/**
* Attach a listener to intercept and handle link clicks in the terminal.
*/
onBeforeHandleLink
:
Event
<
ITerminalBeforeHandleLinkEvent
>
;
readonly
exitCode
:
number
|
undefined
;
processReady
:
Promise
<
void
>
;
...
...
src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
浏览文件 @
8e6a4118
...
...
@@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR
import
{
TerminalConfigHelper
}
from
'
vs/workbench/contrib/terminal/browser/terminalConfigHelper
'
;
import
{
TerminalLinkHandler
}
from
'
vs/workbench/contrib/terminal/browser/terminalLinkHandler
'
;
import
{
IAccessibilityService
}
from
'
vs/platform/accessibility/common/accessibility
'
;
import
{
ITerminalInstanceService
,
ITerminalInstance
,
TerminalShellType
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
ITerminalInstanceService
,
ITerminalInstance
,
TerminalShellType
,
ITerminalBeforeHandleLinkEvent
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
TerminalProcessManager
}
from
'
vs/workbench/contrib/terminal/browser/terminalProcessManager
'
;
import
{
Terminal
as
XTermTerminal
,
IBuffer
,
ITerminalAddon
}
from
'
xterm
'
;
import
{
SearchAddon
,
ISearchOptions
}
from
'
xterm-addon-search
'
;
...
...
@@ -272,6 +272,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
public
get
onMaximumDimensionsChanged
():
Event
<
void
>
{
return
this
.
_onMaximumDimensionsChanged
.
event
;
}
private
readonly
_onFocus
=
new
Emitter
<
ITerminalInstance
>
();
public
get
onFocus
():
Event
<
ITerminalInstance
>
{
return
this
.
_onFocus
.
event
;
}
private
readonly
_onBeforeHandleLink
=
new
Emitter
<
ITerminalBeforeHandleLinkEvent
>
();
public
get
onBeforeHandleLink
():
Event
<
ITerminalBeforeHandleLinkEvent
>
{
return
this
.
_onBeforeHandleLink
.
event
;
}
public
constructor
(
private
readonly
_terminalFocusContextKey
:
IContextKey
<
boolean
>
,
...
...
@@ -523,6 +525,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
});
}
this
.
_linkHandler
=
this
.
_instantiationService
.
createInstance
(
TerminalLinkHandler
,
xterm
,
this
.
_processManager
,
this
.
_configHelper
);
this
.
_linkHandler
.
onBeforeHandleLink
(
e
=>
{
e
.
terminal
=
this
;
this
.
_onBeforeHandleLink
.
fire
(
e
);
});
});
this
.
_commandTrackerAddon
=
new
CommandTrackerAddon
();
...
...
src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts
浏览文件 @
8e6a4118
...
...
@@ -16,9 +16,11 @@ import { IFileService } from 'vs/platform/files/common/files';
import
{
Terminal
,
ILinkMatcherOptions
,
IViewportRange
}
from
'
xterm
'
;
import
{
REMOTE_HOST_SCHEME
}
from
'
vs/platform/remote/common/remoteHosts
'
;
import
{
posix
,
win32
}
from
'
vs/base/common/path
'
;
import
{
ITerminalInstanceService
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
ITerminalInstanceService
,
ITerminalBeforeHandleLinkEvent
,
LINK_INTERCEPT_THRESHOLD
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
OperatingSystem
,
isMacintosh
}
from
'
vs/base/common/platform
'
;
import
{
IMarkdownString
,
MarkdownString
}
from
'
vs/base/common/htmlContent
'
;
import
{
Emitter
,
Event
}
from
'
vs/base/common/event
'
;
import
{
ILogService
}
from
'
vs/platform/log/common/log
'
;
const
pathPrefix
=
'
(
\\
.
\\
.?|
\\
~)
'
;
const
pathSeparatorClause
=
'
\\
/
'
;
...
...
@@ -58,7 +60,7 @@ const CUSTOM_LINK_PRIORITY = -1;
/** Lowest */
const
LOCAL_LINK_PRIORITY
=
-
2
;
export
type
XtermLinkMatcherHandler
=
(
event
:
MouseEvent
,
uri
:
string
)
=>
boolean
|
void
;
export
type
XtermLinkMatcherHandler
=
(
event
:
MouseEvent
,
link
:
string
)
=>
Promise
<
void
>
;
export
type
XtermLinkMatcherValidationCallback
=
(
uri
:
string
,
callback
:
(
isValid
:
boolean
)
=>
void
)
=>
void
;
interface
IPath
{
...
...
@@ -66,14 +68,28 @@ interface IPath {
normalize
(
path
:
string
):
string
;
}
export
class
TerminalLinkHandler
{
private
readonly
_hoverDisposables
=
new
DisposableStore
();
export
class
TerminalLinkHandler
extends
DisposableStore
{
private
_widgetManager
:
TerminalWidgetManager
|
undefined
;
private
_processCwd
:
string
|
undefined
;
private
_gitDiffPreImagePattern
:
RegExp
;
private
_gitDiffPostImagePattern
:
RegExp
;
private
readonly
_tooltipCallback
:
(
event
:
MouseEvent
,
uri
:
string
,
location
:
IViewportRange
)
=>
boolean
|
void
;
private
readonly
_leaveCallback
:
()
=>
void
;
private
_hasBeforeHandleLinkListeners
=
false
;
protected
static
_LINK_INTERCEPT_THRESHOLD
=
LINK_INTERCEPT_THRESHOLD
;
public
static
readonly
LINK_INTERCEPT_THRESHOLD
=
TerminalLinkHandler
.
_LINK_INTERCEPT_THRESHOLD
;
private
readonly
_onBeforeHandleLink
=
this
.
add
(
new
Emitter
<
ITerminalBeforeHandleLinkEvent
>
({
onFirstListenerAdd
:
()
=>
this
.
_hasBeforeHandleLinkListeners
=
true
,
onLastListenerRemove
:
()
=>
this
.
_hasBeforeHandleLinkListeners
=
false
}));
/**
* Allows intercepting links and handling them outside of the default link handler. When fired
* the listener has a set amount of time to handle the link or the default handler will fire.
* This was designed to only be handled by a single listener.
*/
public
get
onBeforeHandleLink
():
Event
<
ITerminalBeforeHandleLinkEvent
>
{
return
this
.
_onBeforeHandleLink
.
event
;
}
constructor
(
private
_xterm
:
Terminal
,
...
...
@@ -83,8 +99,11 @@ export class TerminalLinkHandler {
@
IEditorService
private
readonly
_editorService
:
IEditorService
,
@
IConfigurationService
private
readonly
_configurationService
:
IConfigurationService
,
@
ITerminalInstanceService
private
readonly
_terminalInstanceService
:
ITerminalInstanceService
,
@
IFileService
private
readonly
_fileService
:
IFileService
@
IFileService
private
readonly
_fileService
:
IFileService
,
@
ILogService
private
readonly
_logService
:
ILogService
)
{
super
();
// Matches '--- a/src/file1', capturing 'src/file1' in group 1
this
.
_gitDiffPreImagePattern
=
/^--- a
\/(\S
*
)
/
;
// Matches '+++ b/src/file1', capturing 'src/file1' in group 1
...
...
@@ -211,19 +230,40 @@ export class TerminalLinkHandler {
this
.
_xterm
.
registerLinkMatcher
(
this
.
_gitDiffPostImagePattern
,
wrappedHandler
,
options
);
}
public
dispose
():
void
{
this
.
_hoverDisposables
.
dispose
();
}
private
_wrapLinkHandler
(
handler
:
(
uri
:
string
)
=>
boolean
|
void
):
XtermLinkMatcherHandler
{
return
(
event
:
MouseEvent
,
uri
:
string
)
=>
{
protected
_wrapLinkHandler
(
handler
:
(
link
:
string
)
=>
void
):
XtermLinkMatcherHandler
{
return
async
(
event
:
MouseEvent
,
link
:
string
)
=>
{
// Prevent default electron link handling so Alt+Click mode works normally
event
.
preventDefault
();
// Require correct modifier on click
if
(
!
this
.
_isLinkActivationModifierDown
(
event
))
{
return
false
;
return
;
}
return
handler
(
uri
);
// Allow the link to be intercepted if there are listeners
if
(
this
.
_hasBeforeHandleLinkListeners
)
{
const
wasHandled
=
await
new
Promise
<
boolean
>
(
r
=>
{
const
timeoutId
=
setTimeout
(()
=>
{
canceled
=
true
;
this
.
_logService
.
error
(
'
An extension intecepted a terminal link but did not return
'
);
r
(
false
);
},
TerminalLinkHandler
.
LINK_INTERCEPT_THRESHOLD
);
let
canceled
=
false
;
const
resolve
=
(
handled
:
boolean
)
=>
{
if
(
!
canceled
)
{
clearTimeout
(
timeoutId
);
r
(
handled
);
}
};
this
.
_onBeforeHandleLink
.
fire
({
link
,
resolve
});
});
if
(
!
wasHandled
)
{
handler
(
link
);
}
return
;
}
// Just call the handler if there is no before listener
handler
(
link
);
};
}
...
...
@@ -244,18 +284,17 @@ export class TerminalLinkHandler {
return
this
.
_gitDiffPostImagePattern
;
}
private
_handleLocalLink
(
link
:
string
):
PromiseLike
<
any
>
{
return
this
.
_resolvePath
(
link
).
then
(
resolvedLink
=>
{
if
(
!
resolvedLink
)
{
return
Promise
.
resolve
(
null
);
}
const
lineColumnInfo
:
LineColumnInfo
=
this
.
extractLineColumnInfo
(
link
);
const
selection
:
ITextEditorSelection
=
{
startLineNumber
:
lineColumnInfo
.
lineNumber
,
startColumn
:
lineColumnInfo
.
columnNumber
};
return
this
.
_editorService
.
openEditor
({
resource
:
resolvedLink
,
options
:
{
pinned
:
true
,
selection
}
});
});
private
async
_handleLocalLink
(
link
:
string
):
Promise
<
void
>
{
const
resolvedLink
=
await
this
.
_resolvePath
(
link
);
if
(
!
resolvedLink
)
{
return
;
}
const
lineColumnInfo
:
LineColumnInfo
=
this
.
extractLineColumnInfo
(
link
);
const
selection
:
ITextEditorSelection
=
{
startLineNumber
:
lineColumnInfo
.
lineNumber
,
startColumn
:
lineColumnInfo
.
columnNumber
};
await
this
.
_editorService
.
openEditor
({
resource
:
resolvedLink
,
options
:
{
pinned
:
true
,
selection
}
});
}
private
_validateLocalLink
(
link
:
string
,
callback
:
(
isValid
:
boolean
)
=>
void
):
void
{
...
...
@@ -270,7 +309,7 @@ export class TerminalLinkHandler {
this
.
_openerService
.
open
(
url
,
{
allowTunneling
:
!!
(
this
.
_processManager
&&
this
.
_processManager
.
remoteAuthority
)
});
}
pr
ivate
_isLinkActivationModifierDown
(
event
:
MouseEvent
):
boolean
{
pr
otected
_isLinkActivationModifierDown
(
event
:
MouseEvent
):
boolean
{
const
editorConf
=
this
.
_configurationService
.
getValue
<
{
multiCursorModifier
:
'
ctrlCmd
'
|
'
alt
'
}
>
(
'
editor
'
);
if
(
editorConf
.
multiCursorModifier
===
'
ctrlCmd
'
)
{
return
!!
event
.
altKey
;
...
...
@@ -346,19 +385,19 @@ export class TerminalLinkHandler {
return
link
;
}
private
_resolvePath
(
link
:
string
):
PromiseLike
<
URI
|
null
>
{
private
async
_resolvePath
(
link
:
string
):
Promise
<
URI
|
undefined
>
{
if
(
!
this
.
_processManager
)
{
throw
new
Error
(
'
Process manager is required
'
);
}
const
preprocessedLink
=
this
.
_preprocessPath
(
link
);
if
(
!
preprocessedLink
)
{
return
Promise
.
resolve
(
null
)
;
return
undefined
;
}
const
linkUrl
=
this
.
extractLinkUrl
(
preprocessedLink
);
if
(
!
linkUrl
)
{
return
Promise
.
resolve
(
null
)
;
return
undefined
;
}
try
{
...
...
@@ -373,18 +412,20 @@ export class TerminalLinkHandler {
uri
=
URI
.
file
(
linkUrl
);
}
return
this
.
_fileService
.
resolve
(
uri
).
then
(
stat
=>
{
try
{
const
stat
=
await
this
.
_fileService
.
resolve
(
uri
);
if
(
stat
.
isDirectory
)
{
return
null
;
return
undefined
;
}
return
uri
;
}).
catch
(()
=>
{
}
catch
(
e
)
{
// Does not exist
return
null
;
}
);
return
undefined
;
}
}
catch
{
// Errors in parsing the path
return
Promise
.
resolve
(
null
)
;
return
undefined
;
}
}
...
...
src/vs/workbench/contrib/terminal/browser/terminalService.ts
浏览文件 @
8e6a4118
...
...
@@ -15,7 +15,7 @@ import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import
{
IInstantiationService
,
optional
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
IExtensionService
}
from
'
vs/workbench/services/extensions/common/extensions
'
;
import
{
TerminalInstance
}
from
'
vs/workbench/contrib/terminal/browser/terminalInstance
'
;
import
{
ITerminalService
,
ITerminalInstance
,
ITerminalTab
,
TerminalShellType
,
WindowsShellType
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
ITerminalService
,
ITerminalInstance
,
ITerminalTab
,
TerminalShellType
,
WindowsShellType
,
TerminalLinkHandlerCallback
,
LINK_INTERCEPT_THRESHOLD
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
IRemoteAgentService
}
from
'
vs/workbench/services/remote/common/remoteAgentService
'
;
import
{
TerminalConfigHelper
}
from
'
vs/workbench/contrib/terminal/browser/terminalConfigHelper
'
;
import
{
IQuickInputService
,
IQuickPickItem
,
IPickOptions
}
from
'
vs/platform/quickinput/common/quickInput
'
;
...
...
@@ -30,6 +30,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import
{
find
}
from
'
vs/base/common/arrays
'
;
import
{
timeout
}
from
'
vs/base/common/async
'
;
import
{
IViewsService
}
from
'
vs/workbench/common/views
'
;
import
{
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
interface
IExtHostReadyEntry
{
promise
:
Promise
<
void
>
;
...
...
@@ -50,6 +51,7 @@ export class TerminalService implements ITerminalService {
private
_findState
:
FindReplaceState
;
private
_extHostsReady
:
{
[
authority
:
string
]:
IExtHostReadyEntry
|
undefined
}
=
{};
private
_activeTabIndex
:
number
;
private
_linkHandlers
:
{
[
key
:
string
]:
TerminalLinkHandlerCallback
}
=
{};
public
get
activeTabIndex
():
number
{
return
this
.
_activeTabIndex
;
}
public
get
terminalInstances
():
ITerminalInstance
[]
{
return
this
.
_terminalInstances
;
}
...
...
@@ -411,6 +413,50 @@ export class TerminalService implements ITerminalService {
instance
.
addDisposable
(
instance
.
onDimensionsChanged
(()
=>
this
.
_onInstanceDimensionsChanged
.
fire
(
instance
)));
instance
.
addDisposable
(
instance
.
onMaximumDimensionsChanged
(()
=>
this
.
_onInstanceMaximumDimensionsChanged
.
fire
(
instance
)));
instance
.
addDisposable
(
instance
.
onFocus
(
this
.
_onActiveInstanceChanged
.
fire
,
this
.
_onActiveInstanceChanged
));
instance
.
addDisposable
(
instance
.
onBeforeHandleLink
(
async
e
=>
{
// No link handlers have been registered
const
keys
=
Object
.
keys
(
this
.
_linkHandlers
);
if
(
keys
.
length
===
0
)
{
e
.
resolve
(
false
);
return
;
}
// Fire each link interceptor and wait for either a true, all false or the cancel time
let
resolved
=
false
;
const
promises
:
Promise
<
boolean
>
[]
=
[];
const
timeout
=
setTimeout
(()
=>
{
resolved
=
true
;
e
.
resolve
(
false
);
},
LINK_INTERCEPT_THRESHOLD
);
for
(
let
i
=
0
;
i
<
keys
.
length
;
i
++
)
{
const
p
=
this
.
_linkHandlers
[
keys
[
i
]](
e
);
p
.
then
(
handled
=>
{
if
(
!
resolved
&&
handled
)
{
resolved
=
true
;
clearTimeout
(
timeout
);
e
.
resolve
(
true
);
}
});
promises
.
push
(
p
);
}
await
Promise
.
all
(
promises
);
if
(
!
resolved
)
{
resolved
=
true
;
clearTimeout
(
timeout
);
e
.
resolve
(
false
);
}
}));
}
public
addLinkHandler
(
key
:
string
,
callback
:
TerminalLinkHandlerCallback
):
IDisposable
{
this
.
_linkHandlers
[
key
]
=
callback
;
return
{
dispose
:
()
=>
{
if
(
this
.
_linkHandlers
[
key
]
===
callback
)
{
delete
this
.
_linkHandlers
[
key
];
}
}
};
}
private
_getTabForInstance
(
instance
:
ITerminalInstance
):
ITerminalTab
|
undefined
{
...
...
src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts
浏览文件 @
8e6a4118
...
...
@@ -5,11 +5,12 @@
import
*
as
assert
from
'
assert
'
;
import
{
OperatingSystem
}
from
'
vs/base/common/platform
'
;
import
{
TerminalLinkHandler
,
LineColumnInfo
}
from
'
vs/workbench/contrib/terminal/browser/terminalLinkHandler
'
;
import
{
TerminalLinkHandler
,
LineColumnInfo
,
XtermLinkMatcherHandler
}
from
'
vs/workbench/contrib/terminal/browser/terminalLinkHandler
'
;
import
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
ITerminalInstanceService
}
from
'
vs/workbench/contrib/terminal/browser/terminal
'
;
import
{
Event
}
from
'
vs/base/common/event
'
;
import
{
ITerminalConfigHelper
}
from
'
vs/workbench/contrib/terminal/common/terminal
'
;
import
{
TestConfigurationService
}
from
'
vs/platform/configuration/test/common/testConfigurationService
'
;
class
TestTerminalLinkHandler
extends
TerminalLinkHandler
{
public
get
localLinkRegex
():
RegExp
{
...
...
@@ -24,6 +25,13 @@ class TestTerminalLinkHandler extends TerminalLinkHandler {
public
preprocessPath
(
link
:
string
):
string
|
null
{
return
this
.
_preprocessPath
(
link
);
}
protected
_isLinkActivationModifierDown
(
event
:
MouseEvent
):
boolean
{
return
true
;
}
public
wrapLinkHandler
(
handler
:
(
link
:
string
)
=>
void
):
XtermLinkMatcherHandler
{
TerminalLinkHandler
.
_LINK_INTERCEPT_THRESHOLD
=
0
;
return
this
.
_wrapLinkHandler
(
handler
);
}
}
class
TestXterm
{
...
...
@@ -81,7 +89,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
terminalLinkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Windows
,
userHome
:
''
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
function
testLink
(
link
:
string
,
linkUrl
:
string
,
lineNo
?:
string
,
columnNo
?:
string
)
{
assert
.
equal
(
terminalLinkHandler
.
extractLinkUrl
(
link
),
linkUrl
);
assert
.
equal
(
terminalLinkHandler
.
extractLinkUrl
(
`:
${
link
}
:`
),
linkUrl
);
...
...
@@ -157,7 +165,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
terminalLinkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Linux
,
userHome
:
''
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
function
testLink
(
link
:
string
,
linkUrl
:
string
,
lineNo
?:
string
,
columnNo
?:
string
)
{
assert
.
equal
(
terminalLinkHandler
.
extractLinkUrl
(
link
),
linkUrl
);
assert
.
equal
(
terminalLinkHandler
.
extractLinkUrl
(
`:
${
link
}
:`
),
linkUrl
);
...
...
@@ -225,7 +233,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
linkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Windows
,
userHome
:
'
C:
\\
Users
\\
Me
'
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
linkHandler
.
processCwd
=
'
C:
\\
base
'
;
assert
.
equal
(
linkHandler
.
preprocessPath
(
'
./src/file1
'
),
'
C:
\\
base
\\
src
\\
file1
'
);
...
...
@@ -238,7 +246,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
linkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Windows
,
userHome
:
'
C:
\\
Users
\\
M e
'
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
linkHandler
.
processCwd
=
'
C:
\\
base dir
'
;
assert
.
equal
(
linkHandler
.
preprocessPath
(
'
./src/file1
'
),
'
C:
\\
base dir
\\
src
\\
file1
'
);
...
...
@@ -252,7 +260,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
linkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Linux
,
userHome
:
'
/home/me
'
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
linkHandler
.
processCwd
=
'
/base
'
;
assert
.
equal
(
linkHandler
.
preprocessPath
(
'
./src/file1
'
),
'
/base/src/file1
'
);
...
...
@@ -265,7 +273,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
linkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Linux
,
userHome
:
'
/home/me
'
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
assert
.
equal
(
linkHandler
.
preprocessPath
(
'
./src/file1
'
),
null
);
assert
.
equal
(
linkHandler
.
preprocessPath
(
'
src/file2
'
),
null
);
...
...
@@ -279,7 +287,7 @@ suite('Workbench - TerminalLinkHandler', () => {
const
linkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Linux
,
userHome
:
''
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
);
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
null
!
,
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
function
assertAreGoodMatches
(
matches
:
RegExpMatchArray
|
null
)
{
if
(
matches
)
{
...
...
@@ -302,4 +310,35 @@ suite('Workbench - TerminalLinkHandler', () => {
assert
.
equal
(
linkHandler
.
gitDiffLinkPostImageRegex
.
test
(
'
+++ /dev/null
'
),
false
);
assert
.
equal
(
linkHandler
.
gitDiffLinkPostImageRegex
.
test
(
'
+++ /dev/null
'
),
false
);
});
suite
.
only
(
'
wrapLinkHandler
'
,
()
=>
{
const
nullMouseEvent
:
any
=
Object
.
freeze
({
preventDefault
:
()
=>
{
}
});
test
(
'
should allow intercepting of links with onBeforeHandleLink
'
,
async
()
=>
{
const
linkHandler
=
new
TestTerminalLinkHandler
(
new
TestXterm
()
as
any
,
{
os
:
OperatingSystem
.
Linux
,
userHome
:
''
}
as
any
,
testConfigHelper
,
null
!
,
null
!
,
new
TestConfigurationService
(),
new
MockTerminalInstanceService
(),
null
!
,
null
!
);
linkHandler
.
onBeforeHandleLink
(
e
=>
{
if
(
e
.
link
===
'
https://www.microsoft.com
'
)
{
intercepted
=
true
;
e
.
resolve
(
true
);
}
e
.
resolve
(
false
);
});
const
wrappedHandler
=
linkHandler
.
wrapLinkHandler
(()
=>
defaultHandled
=
true
);
let
defaultHandled
=
false
;
let
intercepted
=
false
;
await
wrappedHandler
(
nullMouseEvent
,
'
https://www.visualstudio.com
'
);
assert
.
equal
(
intercepted
,
false
);
assert
.
equal
(
defaultHandled
,
true
);
defaultHandled
=
false
;
intercepted
=
false
;
await
wrappedHandler
(
nullMouseEvent
,
'
https://www.microsoft.com
'
);
assert
.
equal
(
intercepted
,
true
);
assert
.
equal
(
defaultHandled
,
false
);
});
});
});
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录