Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
bd0e1b8d
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,发现更多精彩内容 >>
未验证
提交
bd0e1b8d
编写于
10月 28, 2020
作者:
C
Connor Peet
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'connor4312/typeahead-style-improvements'
上级
e24b922c
75fc94a8
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
333 addition
and
58 deletion
+333
-58
src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts
...kbench/contrib/terminal/browser/terminalTypeAheadAddon.ts
+262
-40
src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts
src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts
+8
-0
src/vs/workbench/contrib/terminal/test/browser/terminalTypeahead.test.ts
...h/contrib/terminal/test/browser/terminalTypeahead.test.ts
+63
-18
未找到文件。
src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts
浏览文件 @
bd0e1b8d
...
...
@@ -9,6 +9,7 @@ import { Emitter } from 'vs/base/common/event';
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
ITelemetryService
}
from
'
vs/platform/telemetry/common/telemetry
'
;
import
{
TerminalConfigHelper
}
from
'
vs/workbench/contrib/terminal/browser/terminalConfigHelper
'
;
import
{
XTermAttributes
,
XTermCore
}
from
'
vs/workbench/contrib/terminal/browser/xterm-private
'
;
import
{
IBeforeProcessDataEvent
,
ITerminalConfiguration
,
ITerminalProcessManager
}
from
'
vs/workbench/contrib/terminal/common/terminal
'
;
import
type
{
IBuffer
,
IBufferCell
,
ITerminalAddon
,
Terminal
}
from
'
xterm
'
;
...
...
@@ -37,6 +38,9 @@ const statsToggleOffThreshold = 0.5; // if latency is less than `threshold * thi
*/
const
PREDICTION_OMIT_RE
=
/^
(\x
1b
\[\??
25
[
hl
])
+/
;
const
core
=
(
terminal
:
Terminal
):
XTermCore
=>
(
terminal
as
any
).
_core
;
const
flushOutput
=
(
terminal
:
Terminal
)
=>
core
(
terminal
).
writeSync
(
''
);
const
enum
CursorMoveDirection
{
Back
=
'
D
'
,
Forwards
=
'
C
'
,
...
...
@@ -156,6 +160,13 @@ const enum MatchResult {
}
export
interface
IPrediction
{
/**
* Whether applying this prediction can modify the style attributes of the
* terminal. If so it means we need to reset the cursor style if it's
* rolled back.
*/
readonly
affectsStyle
?:
boolean
;
/**
* Returns a sequence to apply the prediction.
* @param buffer to write to
...
...
@@ -329,22 +340,25 @@ class TentativeBoundary implements IPrediction {
* Prediction for a single alphanumeric character.
*/
class
CharacterPrediction
implements
IPrediction
{
public
readonly
affectsStyle
=
true
;
protected
appliedAt
?:
{
pos
:
ICoordinate
,
pos
:
ICoordinate
;
oldAttributes
:
string
;
oldChar
:
string
;
};
constructor
(
private
readonly
style
:
string
,
private
readonly
char
:
string
)
{
}
constructor
(
private
readonly
style
:
TypeAheadStyle
,
private
readonly
char
:
string
)
{
}
public
apply
(
buffer
:
IBuffer
,
cursor
:
Cursor
)
{
public
apply
(
_
:
IBuffer
,
cursor
:
Cursor
)
{
const
cell
=
cursor
.
getCell
();
this
.
appliedAt
=
cell
?
{
pos
:
cursor
.
coordinate
,
oldAttributes
:
getBufferCellAttributes
(
cell
),
oldChar
:
cell
.
getChars
()
}
?
{
pos
:
cursor
.
coordinate
,
oldAttributes
:
attributesToSeq
(
cell
),
oldChar
:
cell
.
getChars
()
}
:
{
pos
:
cursor
.
coordinate
,
oldAttributes
:
''
,
oldChar
:
''
};
cursor
.
shift
(
1
);
return
this
.
style
+
this
.
char
+
this
.
appliedAt
.
oldAttributes
;
return
this
.
style
.
apply
+
this
.
char
+
this
.
style
.
undo
;
}
public
rollback
(
cursor
:
Cursor
)
{
...
...
@@ -362,7 +376,7 @@ class CharacterPrediction implements IPrediction {
return
''
;
// not applied
}
return
cursor
.
clone
().
moveTo
(
this
.
appliedAt
.
pos
)
+
this
.
appliedAt
.
oldAttributes
+
input
;
return
cursor
.
clone
().
moveTo
(
this
.
appliedAt
.
pos
)
+
input
;
}
public
matches
(
input
:
StringReader
)
{
...
...
@@ -384,20 +398,32 @@ class CharacterPrediction implements IPrediction {
}
}
class
BackspacePrediction
extends
CharacterPrediction
{
constructor
()
{
super
(
''
,
'
\
b
'
);
}
class
BackspacePrediction
implements
IPrediction
{
protected
appliedAt
?:
{
pos
:
ICoordinate
;
oldAttributes
:
string
;
oldChar
:
string
;
};
public
apply
(
_
:
IBuffer
,
cursor
:
Cursor
)
{
const
cell
=
cursor
.
getCell
();
this
.
appliedAt
=
cell
?
{
pos
:
cursor
.
coordinate
,
oldAttributes
:
getBufferCellAttributes
(
cell
),
oldChar
:
cell
.
getChars
()
}
?
{
pos
:
cursor
.
coordinate
,
oldAttributes
:
attributesToSeq
(
cell
),
oldChar
:
cell
.
getChars
()
}
:
{
pos
:
cursor
.
coordinate
,
oldAttributes
:
''
,
oldChar
:
''
};
return
cursor
.
shift
(
-
1
)
+
DELETE_CHAR
;
}
public
rollback
(
cursor
:
Cursor
)
{
if
(
!
this
.
appliedAt
)
{
return
''
;
// not applied
}
const
{
oldAttributes
,
oldChar
,
pos
}
=
this
.
appliedAt
;
const
r
=
cursor
.
moveTo
(
pos
)
+
(
oldChar
?
`
${
oldAttributes
}${
oldChar
}${
cursor
.
moveTo
(
pos
)}
`
:
DELETE_CHAR
);
return
r
;
}
public
rollForwards
()
{
return
''
;
}
...
...
@@ -483,7 +509,7 @@ class CursorMovePrediction implements IPrediction {
public
apply
(
buffer
:
IBuffer
,
cursor
:
Cursor
)
{
const
prevPosition
=
cursor
.
x
;
const
currentCell
=
cursor
.
getCell
();
const
prevAttrs
=
currentCell
?
getBufferCellAttributes
(
currentCell
)
:
''
;
const
prevAttrs
=
currentCell
?
attributesToSeq
(
currentCell
)
:
''
;
const
{
amount
,
direction
,
moveByWords
}
=
this
;
const
delta
=
direction
===
CursorMoveDirection
.
Back
?
-
1
:
1
;
...
...
@@ -650,7 +676,11 @@ export class PredictionTimeline {
private
readonly
succeededEmitter
=
new
Emitter
<
IPrediction
>
();
public
readonly
onPredictionSucceeded
=
this
.
succeededEmitter
.
event
;
constructor
(
public
readonly
terminal
:
Terminal
)
{
}
public
get
isShowingPredictions
()
{
return
this
.
showPredictions
;
}
constructor
(
public
readonly
terminal
:
Terminal
,
private
readonly
style
:
TypeAheadStyle
)
{
}
public
setShowPredictions
(
show
:
boolean
)
{
if
(
show
===
this
.
showPredictions
)
{
...
...
@@ -668,6 +698,7 @@ export class PredictionTimeline {
const
toApply
=
this
.
expected
.
filter
(({
gen
})
=>
gen
===
this
.
expected
[
0
].
gen
).
map
(({
p
})
=>
p
);
if
(
show
)
{
this
.
cursor
=
undefined
;
this
.
style
.
expectIncomingStyle
(
toApply
.
reduce
((
count
,
p
)
=>
p
.
affectsStyle
?
count
+
1
:
count
,
0
));
this
.
terminal
.
write
(
toApply
.
map
(
p
=>
p
.
apply
(
buffer
,
this
.
getCursor
(
buffer
))).
join
(
''
));
}
else
{
this
.
terminal
.
write
(
toApply
.
reverse
().
map
(
p
=>
p
.
rollback
(
this
.
getCursor
(
buffer
))).
join
(
''
));
...
...
@@ -729,10 +760,13 @@ export class PredictionTimeline {
case
MatchResult
.
Failure
:
// on a failure, roll back all remaining items in this generation
// and clear predictions, since they are no longer valid
output
+=
this
.
expected
.
filter
(
p
=>
p
.
gen
===
startingGen
)
.
reverse
()
.
map
(({
p
})
=>
p
.
rollback
(
this
.
getCursor
(
buffer
)))
.
join
(
''
);
const
rollback
=
this
.
expected
.
filter
(
p
=>
p
.
gen
===
startingGen
).
reverse
();
output
+=
rollback
.
map
(({
p
})
=>
p
.
rollback
(
this
.
getCursor
(
buffer
))).
join
(
''
);
if
(
rollback
.
some
(
r
=>
r
.
p
.
affectsStyle
))
{
// reading the current style should generally be safe, since predictions
// always restore the style if they modify it.
output
+=
attributesToSeq
(
core
(
this
.
terminal
).
_inputHandler
.
_curAttrData
);
}
this
.
expected
=
[];
this
.
cursor
=
undefined
;
this
.
failedEmitter
.
fire
(
prediction
);
...
...
@@ -756,6 +790,9 @@ export class PredictionTimeline {
if
(
gen
!==
this
.
expected
[
0
].
gen
)
{
break
;
}
if
(
p
.
affectsStyle
)
{
this
.
style
.
expectIncomingStyle
();
}
output
+=
p
.
apply
(
buffer
,
this
.
getCursor
(
buffer
));
}
...
...
@@ -788,7 +825,10 @@ export class PredictionTimeline {
if
(
this
.
currentGen
===
this
.
expected
[
0
].
gen
)
{
const
text
=
prediction
.
apply
(
buffer
,
this
.
getCursor
(
buffer
));
if
(
this
.
showPredictions
)
{
if
(
this
.
showPredictions
&&
text
)
{
if
(
prediction
.
affectsStyle
)
{
this
.
style
.
expectIncomingStyle
();
}
// console.log('predict:', JSON.stringify(text));
this
.
terminal
.
write
(
text
);
}
...
...
@@ -804,13 +844,27 @@ export class PredictionTimeline {
* after this one will only be displayed after the give prediction matches
* pty output/
*/
public
addBoundary
(
buffer
:
IBuffer
,
prediction
:
IPrediction
)
{
this
.
addPrediction
(
buffer
,
prediction
);
public
addBoundary
():
void
;
public
addBoundary
(
buffer
:
IBuffer
,
prediction
:
IPrediction
):
void
;
public
addBoundary
(
buffer
?:
IBuffer
,
prediction
?:
IPrediction
)
{
if
(
buffer
&&
prediction
)
{
this
.
addPrediction
(
buffer
,
prediction
);
}
this
.
currentGen
++
;
}
/**
* Peeks the last prediction written.
*/
public
peekEnd
()
{
return
this
.
expected
[
this
.
expected
.
length
-
1
]?.
p
;
}
public
getCursor
(
buffer
:
IBuffer
)
{
if
(
!
this
.
cursor
)
{
if
(
this
.
showPredictions
)
{
flushOutput
(
this
.
terminal
);
}
this
.
cursor
=
new
Cursor
(
this
.
terminal
.
rows
,
this
.
terminal
.
cols
,
buffer
);
}
...
...
@@ -829,7 +883,7 @@ export class PredictionTimeline {
/**
* Gets the escape sequence to restore state/appearence in the cell.
*/
const
getBufferCellAttributes
=
(
cell
:
IBufferCell
)
=>
cell
.
isAttributeDefault
()
const
attributesToSeq
=
(
cell
:
XTermAttributes
)
=>
cell
.
isAttributeDefault
()
?
`
${
CSI
}
0m`
:
[
cell
.
isBold
()
&&
`
${
CSI
}
1m`
,
...
...
@@ -849,26 +903,179 @@ const getBufferCellAttributes = (cell: IBufferCell) => cell.isAttributeDefault()
cell
.
isBgDefault
()
&&
`
${
CSI
}
49m`
,
].
filter
(
seq
=>
!!
seq
).
join
(
''
);
const
parseTypeheadStyle
=
(
style
:
ITerminalConfiguration
[
'
typeaheadStyle
'
])
=>
{
switch
(
style
)
{
case
'
bold
'
:
return
`
${
CSI
}
1m`
;
case
'
dim
'
:
return
`
${
CSI
}
2m`
;
case
'
italic
'
:
return
`
${
CSI
}
3m`
;
case
'
underlined
'
:
return
`
${
CSI
}
4m`
;
case
'
inverted
'
:
return
`
${
CSI
}
7m`
;
default
:
const
{
r
,
g
,
b
}
=
Color
.
fromHex
(
style
).
rgba
;
return
`
${
CSI
}
38;2;
${
r
}
;
${
g
}
;
${
b
}
m`
;
const
arrayHasPrefixAt
=
<
T
>
(
a
:
ReadonlyArray
<
T
>
,
ai
:
number
,
b
:
ReadonlyArray
<
T
>
)
=>
{
if
(
a
.
length
-
ai
>
b
.
length
)
{
return
false
;
}
for
(
let
bi
=
0
;
bi
<
b
.
length
;
bi
++
,
ai
++
)
{
if
(
b
[
ai
]
!==
a
[
ai
])
{
return
false
;
}
}
return
true
;
};
/**
* @see https://github.com/xtermjs/xterm.js/blob/065eb13a9d3145bea687239680ec9696d9112b8e/src/common/InputHandler.ts#L2127
*/
const
getColorWidth
=
(
params
:
(
number
|
number
[])[],
pos
:
number
)
=>
{
const
accu
=
[
0
,
0
,
-
1
,
0
,
0
,
0
];
let
cSpace
=
0
;
let
advance
=
0
;
do
{
const
v
=
params
[
pos
+
advance
];
accu
[
advance
+
cSpace
]
=
typeof
v
===
'
number
'
?
v
:
v
[
0
];
if
(
typeof
v
!==
'
number
'
)
{
let
i
=
0
;
do
{
if
(
accu
[
1
]
===
5
)
{
cSpace
=
1
;
}
accu
[
advance
+
i
+
1
+
cSpace
]
=
v
[
i
];
}
while
(
++
i
<
v
.
length
&&
i
+
advance
+
1
+
cSpace
<
accu
.
length
);
break
;
}
// exit early if can decide color mode with semicolons
if
((
accu
[
1
]
===
5
&&
advance
+
cSpace
>=
2
)
||
(
accu
[
1
]
===
2
&&
advance
+
cSpace
>=
5
))
{
break
;
}
// offset colorSpace slot for semicolon mode
if
(
accu
[
1
])
{
cSpace
=
1
;
}
}
while
(
++
advance
+
pos
<
params
.
length
&&
advance
+
cSpace
<
accu
.
length
);
return
advance
;
};
class
TypeAheadStyle
{
private
static
compileArgs
(
args
:
ReadonlyArray
<
number
>
)
{
return
`
${
CSI
}${
args
.
join
(
'
;
'
)}
m`
;
}
/**
* Number of typeahead style arguments we expect to read. If this is 0 and
* we see a style coming in, we know that the PTY actually wanted to update.
*/
private
expectedIncomingStyles
=
0
;
private
applyArgs
!
:
ReadonlyArray
<
number
>
;
private
originalUndoArgs
!
:
ReadonlyArray
<
number
>
;
private
undoArgs
!
:
ReadonlyArray
<
number
>
;
public
apply
!
:
string
;
public
undo
!
:
string
;
constructor
(
value
:
ITerminalConfiguration
[
'
typeaheadStyle
'
])
{
this
.
onUpdate
(
value
);
}
/**
* Signals that a style was written to the terminal and we should watch
* for it coming in.
*/
public
expectIncomingStyle
(
n
=
1
)
{
this
.
expectedIncomingStyles
+=
n
*
2
;
}
/**
* Should be called when an attribut eupdate happens in the terminal.
*/
public
onDidWriteSGR
(
args
:
(
number
|
number
[])[])
{
const
originalUndo
=
this
.
undoArgs
;
for
(
let
i
=
0
;
i
<
args
.
length
;)
{
const
px
=
args
[
i
];
const
p
=
typeof
px
===
'
number
'
?
px
:
px
[
0
];
if
(
this
.
expectedIncomingStyles
)
{
if
(
arrayHasPrefixAt
(
args
,
i
,
this
.
undoArgs
))
{
this
.
expectedIncomingStyles
--
;
i
+=
this
.
undoArgs
.
length
;
continue
;
}
if
(
arrayHasPrefixAt
(
args
,
i
,
this
.
applyArgs
))
{
this
.
expectedIncomingStyles
--
;
i
+=
this
.
applyArgs
.
length
;
continue
;
}
}
const
width
=
p
===
38
||
p
===
48
||
p
===
58
?
getColorWidth
(
args
,
i
)
:
1
;
switch
(
this
.
applyArgs
[
0
])
{
case
1
:
if
(
p
===
2
)
{
this
.
undoArgs
=
[
22
,
2
];
}
else
if
(
p
===
22
||
p
===
0
)
{
this
.
undoArgs
=
[
22
];
}
break
;
case
2
:
if
(
p
===
1
)
{
this
.
undoArgs
=
[
22
,
1
];
}
else
if
(
p
===
22
||
p
===
0
)
{
this
.
undoArgs
=
[
22
];
}
break
;
case
38
:
if
(
p
===
0
||
p
===
39
||
p
===
100
)
{
this
.
undoArgs
=
[
39
];
}
else
if
((
p
>=
30
&&
p
<=
38
)
||
(
p
>=
90
&&
p
<=
97
))
{
this
.
undoArgs
=
args
.
slice
(
i
,
i
+
width
)
as
number
[];
}
break
;
default
:
if
(
p
===
this
.
applyArgs
[
0
])
{
this
.
undoArgs
=
this
.
applyArgs
;
}
else
if
(
p
===
0
)
{
this
.
undoArgs
=
this
.
originalUndoArgs
;
}
// no-op
}
i
+=
width
;
}
if
(
originalUndo
!==
this
.
undoArgs
)
{
this
.
undo
=
TypeAheadStyle
.
compileArgs
(
this
.
undoArgs
);
}
}
/**
* Updates the current typeahead style.
*/
public
onUpdate
(
style
:
ITerminalConfiguration
[
'
typeaheadStyle
'
])
{
const
{
applyArgs
,
undoArgs
}
=
this
.
getArgs
(
style
);
this
.
applyArgs
=
applyArgs
;
this
.
undoArgs
=
this
.
originalUndoArgs
=
undoArgs
;
this
.
apply
=
TypeAheadStyle
.
compileArgs
(
this
.
applyArgs
);
this
.
undo
=
TypeAheadStyle
.
compileArgs
(
this
.
undoArgs
);
}
private
getArgs
(
style
:
ITerminalConfiguration
[
'
typeaheadStyle
'
])
{
switch
(
style
)
{
case
'
bold
'
:
return
{
applyArgs
:
[
1
],
undoArgs
:
[
22
]
};
case
'
dim
'
:
return
{
applyArgs
:
[
2
],
undoArgs
:
[
22
]
};
case
'
italic
'
:
return
{
applyArgs
:
[
3
],
undoArgs
:
[
23
]
};
case
'
underlined
'
:
return
{
applyArgs
:
[
4
],
undoArgs
:
[
24
]
};
case
'
inverted
'
:
return
{
applyArgs
:
[
7
],
undoArgs
:
[
27
]
};
default
:
const
{
r
,
g
,
b
}
=
Color
.
fromHex
(
style
).
rgba
;
return
{
applyArgs
:
[
38
,
2
,
r
,
g
,
b
],
undoArgs
:
[
39
]
};
}
}
}
export
class
TypeAheadAddon
extends
Disposable
implements
ITerminalAddon
{
private
type
headStyle
=
parseType
headStyle
(
this
.
config
.
config
.
typeaheadStyle
);
private
type
aheadStyle
=
new
TypeA
headStyle
(
this
.
config
.
config
.
typeaheadStyle
);
private
typeaheadThreshold
=
this
.
config
.
config
.
typeaheadThreshold
;
private
lastRow
?:
{
y
:
number
;
startingX
:
number
};
private
timeline
?:
PredictionTimeline
;
...
...
@@ -883,10 +1090,14 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
}
public
activate
(
terminal
:
Terminal
):
void
{
const
timeline
=
this
.
timeline
=
new
PredictionTimeline
(
terminal
);
const
timeline
=
this
.
timeline
=
new
PredictionTimeline
(
terminal
,
this
.
typeaheadStyle
);
const
stats
=
this
.
stats
=
this
.
_register
(
new
PredictionStats
(
this
.
timeline
));
timeline
.
setShowPredictions
(
this
.
typeaheadThreshold
===
0
);
this
.
_register
(
terminal
.
parser
.
registerCsiHandler
({
final
:
'
m
'
},
args
=>
{
this
.
typeaheadStyle
.
onDidWriteSGR
(
args
);
return
false
;
}));
this
.
_register
(
terminal
.
onData
(
e
=>
this
.
onUserData
(
e
)));
this
.
_register
(
terminal
.
onResize
(()
=>
{
timeline
.
setShowPredictions
(
false
);
...
...
@@ -894,7 +1105,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
this
.
reevaluatePredictorState
(
stats
,
timeline
);
}));
this
.
_register
(
this
.
config
.
onConfigChanged
(()
=>
{
this
.
type
headStyle
=
parseTypeheadStyl
e
(
this
.
config
.
config
.
typeaheadStyle
);
this
.
type
aheadStyle
.
onUpdat
e
(
this
.
config
.
config
.
typeaheadStyle
);
this
.
typeaheadThreshold
=
this
.
config
.
config
.
typeaheadThreshold
;
this
.
reevaluatePredictorState
(
stats
,
timeline
);
}));
...
...
@@ -989,13 +1200,24 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
const
reader
=
new
StringReader
(
data
);
while
(
reader
.
remaining
>
0
)
{
if
(
reader
.
eatCharCode
(
127
))
{
// backspace
const
previous
=
this
.
timeline
.
peekEnd
();
if
(
previous
&&
previous
instanceof
CharacterPrediction
)
{
this
.
timeline
.
addBoundary
();
}
// backspace must be able to read the previously-written character in
// the event that it needs to undo it
if
(
this
.
timeline
.
isShowingPredictions
)
{
flushOutput
(
this
.
timeline
.
terminal
);
}
addLeftNavigating
(
new
BackspacePrediction
());
continue
;
}
if
(
reader
.
eatCharCode
(
32
,
126
))
{
// alphanum
const
char
=
data
[
reader
.
index
-
1
];
if
(
this
.
timeline
.
addPrediction
(
buffer
,
new
CharacterPrediction
(
this
.
typeheadStyle
,
char
))
&&
this
.
timeline
.
getCursor
(
buffer
).
x
===
terminal
.
cols
)
{
if
(
this
.
timeline
.
addPrediction
(
buffer
,
new
CharacterPrediction
(
this
.
type
a
headStyle
,
char
))
&&
this
.
timeline
.
getCursor
(
buffer
).
x
===
terminal
.
cols
)
{
this
.
timeline
.
addBoundary
(
buffer
,
new
TentativeBoundary
(
new
LinewrapPrediction
()));
}
continue
;
...
...
src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts
浏览文件 @
bd0e1b8d
...
...
@@ -3,6 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
IBufferCell
}
from
'
xterm
'
;
export
type
XTermAttributes
=
Omit
<
IBufferCell
,
'
getWidth
'
|
'
getChars
'
|
'
getCode
'
>
&
{
clone
?():
XTermAttributes
};
export
interface
XTermCore
{
_onScroll
:
IEventEmitter
<
number
>
;
_onKey
:
IEventEmitter
<
{
key
:
string
}
>
;
...
...
@@ -16,6 +20,10 @@ export interface XTermCore {
triggerDataEvent
(
data
:
string
,
wasUserInput
?:
boolean
):
void
;
};
_inputHandler
:
{
_curAttrData
:
XTermAttributes
;
};
_renderService
:
{
dimensions
:
{
actualCellWidth
:
number
;
...
...
src/vs/workbench/contrib/terminal/test/browser/terminalTypeahead.test.ts
浏览文件 @
bd0e1b8d
...
...
@@ -77,7 +77,7 @@ suite('Workbench - Terminal Typeahead', () => {
const
predictedHelloo
=
[
`
${
CSI
}
?25l`
,
// hide cursor
`
${
CSI
}
2;7H`
,
// move cursor
cursor
`
${
CSI
}
2;7H`
,
// move cursor
'
o
'
,
// new character
`
${
CSI
}
2;8H`
,
// place cursor back at end of line
`
${
CSI
}
?25h`
,
// show cursor
...
...
@@ -107,14 +107,14 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
predicts a single character
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
o
'
);
t
.
expectWritten
(
`
${
CSI
}
3mo`
);
t
.
expectWritten
(
`
${
CSI
}
3mo
${
CSI
}
23m
`
);
});
test
(
'
validates character prediction
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
o
'
);
expectProcessed
(
'
o
'
,
predictedHelloo
);
...
...
@@ -122,7 +122,7 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
rolls back character prediction
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
o
'
);
...
...
@@ -130,6 +130,27 @@ suite('Workbench - Terminal Typeahead', () => {
`
${
CSI
}
?25l`
,
// hide cursor
`
${
CSI
}
2;7H`
,
// move cursor cursor
`
${
CSI
}
X`
,
// delete character
`
${
CSI
}
0m`
,
// reset style
'
q
'
,
// new character
`
${
CSI
}
?25h`
,
// show cursor
].
join
(
''
));
assert
.
strictEqual
(
addon
.
stats
?.
accuracy
,
0
);
});
test
(
'
restores cursor graphics mode
'
,
()
=>
{
const
t
=
createMockTerminal
({
lines
:
[
'
hello|
'
],
cursorAttrs
:
{
isAttributeDefault
:
false
,
isBold
:
true
,
isFgPalette
:
true
,
getFgColor
:
1
},
});
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
o
'
);
expectProcessed
(
'
q
'
,
[
`
${
CSI
}
?25l`
,
// hide cursor
`
${
CSI
}
2;7H`
,
// move cursor cursor
`
${
CSI
}
X`
,
// delete character
`
${
CSI
}
1m`
,
// reset style
`
${
CSI
}
38;5;1m`
,
// reset style
'
q
'
,
// new character
`
${
CSI
}
?25h`
,
// show cursor
].
join
(
''
));
...
...
@@ -137,13 +158,13 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
validates against and applies graphics mode on predicted
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
o
'
);
expectProcessed
(
`
${
CSI
}
4mo`
,
[
`
${
CSI
}
?25l`
,
// hide cursor
`
${
CSI
}
2;7H`
,
// move cursor
cursor
`
${
CSI
}
4m`
,
// PTY's style
`
${
CSI
}
2;7H`
,
// move cursor
`
${
CSI
}
4m`
,
//
new
PTY's style
'
o
'
,
// new character
`
${
CSI
}
2;8H`
,
// place cursor back at end of line
`
${
CSI
}
?25h`
,
// show cursor
...
...
@@ -152,13 +173,13 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
ignores cursor hides or shows
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
o
'
);
expectProcessed
(
`
${
CSI
}
?25lo
${
CSI
}
?25h`
,
[
`
${
CSI
}
?25l`
,
// hide cursor from PTY
`
${
CSI
}
?25l`
,
// hide cursor
`
${
CSI
}
2;7H`
,
// move cursor
cursor
`
${
CSI
}
2;7H`
,
// move cursor
'
o
'
,
// new character
`
${
CSI
}
?25h`
,
// show cursor from PTY
`
${
CSI
}
2;8H`
,
// place cursor back at end of line
...
...
@@ -168,7 +189,7 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
matches backspace at EOL (bash style)
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
\
x7F
'
);
expectProcessed
(
`\b
${
CSI
}
K`
,
`\b
${
CSI
}
K`
);
...
...
@@ -176,7 +197,7 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
matches backspace at EOL (zsh style)
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
\
x7F
'
);
expectProcessed
(
'
\
b
\
b
'
,
'
\
b
\
b
'
);
...
...
@@ -184,7 +205,7 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
gradually matches backspace
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
t
.
onData
(
'
\
x7F
'
);
expectProcessed
(
'
\
b
'
,
''
);
...
...
@@ -193,7 +214,7 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
waits for validation before deleting to left of cursor
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
// initially should not backspace (until the server confirms it)
...
...
@@ -214,7 +235,7 @@ suite('Workbench - Terminal Typeahead', () => {
});
test
(
'
avoids predicting password input
'
,
()
=>
{
const
t
=
createMockTerminal
(
'
hello|
'
);
const
t
=
createMockTerminal
(
{
lines
:
[
'
hello|
'
]
}
);
addon
.
activate
(
t
.
terminal
);
expectProcessed
(
'
Your password:
'
,
'
Your password:
'
);
...
...
@@ -223,7 +244,7 @@ suite('Workbench - Terminal Typeahead', () => {
expectProcessed
(
'
\r\n
'
,
'
\r\n
'
);
t
.
onData
(
'
o
'
);
// back to normal mode
t
.
expectWritten
(
`
${
CSI
}
3mo`
);
t
.
expectWritten
(
`
${
CSI
}
3mo
${
CSI
}
23m
`
);
});
});
});
...
...
@@ -245,10 +266,14 @@ function stubPrediction(): IPrediction {
};
}
function
createMockTerminal
(...
lines
:
string
[])
{
function
createMockTerminal
({
lines
,
cursorAttrs
}:
{
lines
:
string
[],
cursorAttrs
?:
any
,
})
{
const
written
:
string
[]
=
[];
const
cursor
=
{
y
:
1
,
x
:
1
};
const
onData
=
new
Emitter
<
string
>
();
const
csiEmitter
=
new
Emitter
<
number
[]
>
();
for
(
let
y
=
0
;
y
<
lines
.
length
;
y
++
)
{
const
line
=
lines
[
y
];
...
...
@@ -269,14 +294,28 @@ function createMockTerminal(...lines: string[]) {
},
clearWritten
:
()
=>
written
.
splice
(
0
,
written
.
length
),
onData
:
(
s
:
string
)
=>
onData
.
fire
(
s
),
csiEmitter
,
terminal
:
{
cols
:
80
,
rows
:
5
,
onResize
:
new
Emitter
<
void
>
().
event
,
onData
:
onData
.
event
,
parser
:
{
registerCsiHandler
(
_
:
unknown
,
callback
:
()
=>
void
)
{
csiEmitter
.
event
(
callback
);
},
},
write
(
line
:
string
)
{
written
.
push
(
line
);
},
_core
:
{
_inputHandler
:
{
_curAttrData
:
mockCell
(
''
,
cursorAttrs
)
},
writeSync
()
{
}
},
buffer
:
{
active
:
{
type
:
'
normal
'
,
...
...
@@ -296,9 +335,13 @@ function createMockTerminal(...lines: string[]) {
};
}
function
mockCell
(
char
:
string
)
{
function
mockCell
(
char
:
string
,
attrs
:
{
[
key
:
string
]:
unknown
}
=
{}
)
{
return
new
Proxy
({},
{
get
(
_
,
prop
)
{
if
(
typeof
prop
===
'
string
'
&&
attrs
.
hasOwnProperty
(
prop
))
{
return
()
=>
attrs
[
prop
];
}
switch
(
prop
)
{
case
'
getWidth
'
:
return
()
=>
1
;
...
...
@@ -306,6 +349,8 @@ function mockCell(char: string) {
return
()
=>
char
;
case
'
getCode
'
:
return
()
=>
char
.
charCodeAt
(
0
)
||
0
;
case
'
isAttributeDefault
'
:
return
()
=>
true
;
default
:
return
String
(
prop
).
startsWith
(
'
is
'
)
?
(()
=>
false
)
:
(()
=>
0
);
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录