Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
da732ab0
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,发现更多精彩内容 >>
提交
da732ab0
编写于
1月 02, 2019
作者:
R
Rob Lourens
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Use new tree for settings editor
上级
1bea381d
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
891 addition
and
1049 deletion
+891
-1049
src/vs/base/browser/ui/list/listView.ts
src/vs/base/browser/ui/list/listView.ts
+4
-0
src/vs/workbench/parts/preferences/browser/settingsTree.ts
src/vs/workbench/parts/preferences/browser/settingsTree.ts
+729
-898
src/vs/workbench/parts/preferences/browser/settingsWidgets.ts
...vs/workbench/parts/preferences/browser/settingsWidgets.ts
+1
-1
src/vs/workbench/parts/preferences/electron-browser/media/settingsEditor2.css
...ts/preferences/electron-browser/media/settingsEditor2.css
+24
-15
src/vs/workbench/parts/preferences/electron-browser/settingsEditor2.ts
...nch/parts/preferences/electron-browser/settingsEditor2.ts
+133
-135
未找到文件。
src/vs/base/browser/ui/list/listView.ts
浏览文件 @
da732ab0
...
...
@@ -374,6 +374,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const
item
=
this
.
items
[
index
];
const
renderer
=
this
.
renderers
.
get
(
item
.
templateId
);
if
(
!
item
.
row
)
{
return
;
}
if
(
renderer
.
disposeElement
)
{
renderer
.
disposeElement
(
item
.
element
,
index
,
item
.
row
!
.
templateData
);
}
...
...
src/vs/workbench/parts/preferences/browser/settingsTree.ts
浏览文件 @
da732ab0
...
...
@@ -12,8 +12,12 @@ import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria';
import
{
Button
}
from
'
vs/base/browser/ui/button/button
'
;
import
{
Checkbox
}
from
'
vs/base/browser/ui/checkbox/checkbox
'
;
import
{
InputBox
}
from
'
vs/base/browser/ui/inputbox/inputBox
'
;
import
{
SelectBox
,
ISelectOptionItem
}
from
'
vs/base/browser/ui/selectBox/selectBox
'
;
import
{
IListVirtualDelegate
}
from
'
vs/base/browser/ui/list/list
'
;
import
{
ISelectOptionItem
,
SelectBox
}
from
'
vs/base/browser/ui/selectBox/selectBox
'
;
import
{
ToolBar
}
from
'
vs/base/browser/ui/toolbar/toolbar
'
;
import
{
IObjectTreeOptions
,
ObjectTree
}
from
'
vs/base/browser/ui/tree/objectTree
'
;
import
{
ObjectTreeModel
}
from
'
vs/base/browser/ui/tree/objectTreeModel
'
;
import
{
ITreeModel
,
ITreeNode
,
ITreeRenderer
,
ITreeFilter
,
TreeVisibility
,
TreeFilterResult
}
from
'
vs/base/browser/ui/tree/tree
'
;
import
{
Action
,
IAction
}
from
'
vs/base/common/actions
'
;
import
*
as
arrays
from
'
vs/base/common/arrays
'
;
import
{
Color
,
RGBA
}
from
'
vs/base/common/color
'
;
...
...
@@ -21,26 +25,24 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import
{
Emitter
,
Event
}
from
'
vs/base/common/event
'
;
import
{
KeyCode
}
from
'
vs/base/common/keyCodes
'
;
import
{
dispose
,
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
ISpliceable
}
from
'
vs/base/common/sequence
'
;
import
{
escapeRegExpCharacters
,
startsWith
}
from
'
vs/base/common/strings
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
IAccessibilityProvider
,
IDataSource
,
IFilter
,
IRenderer
as
ITreeRenderer
,
ITree
,
ITreeConfiguration
}
from
'
vs/base/parts/tree/browser/tree
'
;
import
{
DefaultTreestyler
}
from
'
vs/base/parts/tree/browser/treeDefaults
'
;
import
{
Tree
}
from
'
vs/base/parts/tree/browser/treeImpl
'
;
import
{
ITree
,
IFilter
,
IAccessibilityProvider
}
from
'
vs/base/parts/tree/browser/tree
'
;
import
{
localize
}
from
'
vs/nls
'
;
import
{
IClipboardService
}
from
'
vs/platform/clipboard/common/clipboardService
'
;
import
{
ICommandService
}
from
'
vs/platform/commands/common/commands
'
;
import
{
IConfigurationService
,
ConfigurationTarget
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
ConfigurationTarget
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
IContextMenuService
,
IContextViewService
}
from
'
vs/platform/contextview/browser/contextView
'
;
import
{
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
IKeybindingService
}
from
'
vs/platform/keybinding/common/keybinding
'
;
import
{
WorkbenchTreeController
}
from
'
vs/platform/list/browser/listService
'
;
import
{
IOpenerService
}
from
'
vs/platform/opener/common/opener
'
;
import
{
editorBackground
,
errorForeground
,
focusBorder
,
foreground
,
inputValidationErrorBackground
,
inputValidationError
Foreground
,
inputValidationErrorBorder
}
from
'
vs/platform/theme/common/colorRegistry
'
;
import
{
editorBackground
,
errorForeground
,
focusBorder
,
foreground
,
inputValidationErrorBackground
,
inputValidationError
Border
,
inputValidationErrorForeground
}
from
'
vs/platform/theme/common/colorRegistry
'
;
import
{
attachButtonStyler
,
attachInputBoxStyler
,
attachSelectBoxStyler
,
attachStyler
}
from
'
vs/platform/theme/common/styler
'
;
import
{
ICssStyleCollector
,
ITheme
,
IThemeService
,
registerThemingParticipant
}
from
'
vs/platform/theme/common/themeService
'
;
import
{
ITOCEntry
}
from
'
vs/workbench/parts/preferences/browser/settingsLayout
'
;
import
{
ISettingsEditorViewState
,
isExcludeSetting
,
settingKeyToDisplayFormat
,
SettingsTreeElement
,
SettingsTreeGroupElement
,
SettingsTreeNewExtensionsElement
,
SettingsTreeSettingElement
}
from
'
vs/workbench/parts/preferences/browser/settingsTreeModels
'
;
import
{
ExcludeSettingWidget
,
IExcludeDataItem
,
settingsHeaderForeground
,
settingsNumberInputBackground
,
settingsNumberInputBorder
,
settingsNumberInputForeground
,
settingsSelectBackground
,
settingsSelectBorder
,
settingsSelectForeground
,
settingsSelectListBorder
,
settingsTextInputBackground
,
settingsTextInputBorder
,
settingsTextInputForeground
}
from
'
vs/workbench/parts/preferences/browser/settingsWidgets
'
;
import
{
ISettingsEditorViewState
,
settingKeyToDisplayFormat
,
SettingsTreeElement
,
SettingsTreeGroupChild
,
SettingsTreeGroupElement
,
SettingsTreeNewExtensionsElement
,
SettingsTreeSettingElement
}
from
'
vs/workbench/parts/preferences/browser/settingsTreeModels
'
;
import
{
ExcludeSettingWidget
,
IExclude
ChangeEvent
,
IExclude
DataItem
,
settingsHeaderForeground
,
settingsNumberInputBackground
,
settingsNumberInputBorder
,
settingsNumberInputForeground
,
settingsSelectBackground
,
settingsSelectBorder
,
settingsSelectForeground
,
settingsSelectListBorder
,
settingsTextInputBackground
,
settingsTextInputBorder
,
settingsTextInputForeground
}
from
'
vs/workbench/parts/preferences/browser/settingsWidgets
'
;
import
{
SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU
}
from
'
vs/workbench/parts/preferences/common/preferences
'
;
import
{
ISetting
,
ISettingsGroup
,
SettingValueType
}
from
'
vs/workbench/services/preferences/common/preferences
'
;
...
...
@@ -170,102 +172,6 @@ function getFlatSettings(settingsGroups: ISettingsGroup[]) {
return
result
;
}
export
class
SettingsDataSource
implements
IDataSource
{
getId
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
string
{
return
element
.
id
;
}
hasChildren
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
boolean
{
if
(
element
instanceof
SettingsTreeGroupElement
)
{
return
true
;
}
return
false
;
}
getChildren
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
Promise
<
any
>
{
return
Promise
.
resolve
(
this
.
_getChildren
(
element
));
}
private
_getChildren
(
element
:
SettingsTreeElement
):
SettingsTreeElement
[]
{
if
(
element
instanceof
SettingsTreeGroupElement
)
{
return
element
.
children
;
}
else
{
// No children...
return
null
;
}
}
getParent
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
Promise
<
any
>
{
return
Promise
.
resolve
(
element
&&
element
.
parent
);
}
shouldAutoexpand
():
boolean
{
return
true
;
}
}
export
class
SimplePagedDataSource
implements
IDataSource
{
private
static
readonly
SETTINGS_PER_PAGE
=
30
;
private
static
readonly
BUFFER
=
5
;
private
loadedToIndex
:
number
;
constructor
(
private
realDataSource
:
IDataSource
)
{
this
.
reset
();
}
reset
():
void
{
this
.
loadedToIndex
=
SimplePagedDataSource
.
SETTINGS_PER_PAGE
;
}
pageTo
(
index
:
number
,
top
=
false
):
boolean
{
const
buffer
=
top
?
SimplePagedDataSource
.
SETTINGS_PER_PAGE
:
SimplePagedDataSource
.
BUFFER
;
if
(
index
>
this
.
loadedToIndex
-
buffer
)
{
this
.
loadedToIndex
=
(
Math
.
ceil
(
index
/
SimplePagedDataSource
.
SETTINGS_PER_PAGE
)
+
1
)
*
SimplePagedDataSource
.
SETTINGS_PER_PAGE
;
return
true
;
}
else
{
return
false
;
}
}
getId
(
tree
:
ITree
,
element
:
any
):
string
{
return
this
.
realDataSource
.
getId
(
tree
,
element
);
}
hasChildren
(
tree
:
ITree
,
element
:
any
):
boolean
{
return
this
.
realDataSource
.
hasChildren
(
tree
,
element
);
}
getChildren
(
tree
:
ITree
,
element
:
SettingsTreeGroupElement
):
Promise
<
any
>
{
return
this
.
realDataSource
.
getChildren
(
tree
,
element
).
then
(
realChildren
=>
{
return
this
.
_getChildren
(
realChildren
);
});
}
_getChildren
(
realChildren
:
SettingsTreeElement
[]):
any
[]
{
const
lastChild
=
realChildren
[
realChildren
.
length
-
1
];
if
(
lastChild
&&
lastChild
.
index
>
this
.
loadedToIndex
)
{
return
realChildren
.
filter
(
child
=>
{
return
child
.
index
<
this
.
loadedToIndex
;
});
}
else
{
return
realChildren
;
}
}
getParent
(
tree
:
ITree
,
element
:
any
):
Promise
<
any
>
{
return
this
.
realDataSource
.
getParent
(
tree
,
element
);
}
shouldAutoexpand
(
tree
:
ITree
,
element
:
any
):
boolean
{
return
this
.
realDataSource
.
shouldAutoexpand
(
tree
,
element
);
}
}
interface
IDisposableTemplate
{
toDispose
:
IDisposable
[];
}
...
...
@@ -325,7 +231,7 @@ const SETTINGS_BOOL_TEMPLATE_ID = 'settings.bool.template';
const
SETTINGS_EXCLUDE_TEMPLATE_ID
=
'
settings.exclude.template
'
;
const
SETTINGS_COMPLEX_TEMPLATE_ID
=
'
settings.complex.template
'
;
const
SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID
=
'
settings.newExtensions.template
'
;
const
SETTINGS_
GROUP_
ELEMENT_TEMPLATE_ID
=
'
settings.group.template
'
;
const
SETTINGS_ELEMENT_TEMPLATE_ID
=
'
settings.group.template
'
;
export
interface
ISettingChangeEvent
{
key
:
string
;
...
...
@@ -343,20 +249,22 @@ export interface ISettingOverrideClickEvent {
targetKey
:
string
;
}
export
class
SettingsRenderer
implements
ITreeRenderer
{
export
abstract
class
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeElement
,
never
,
any
>
{
/** To override */
templateId
=
undefined
;
public
static
readonly
CONTROL_CLASS
=
'
setting-control-focus-target
'
;
public
static
readonly
CONTROL_SELECTOR
=
'
.
'
+
Settings
Renderer
.
CONTROL_CLASS
;
public
static
readonly
CONTROL_SELECTOR
=
'
.
'
+
AbstractSetting
Renderer
.
CONTROL_CLASS
;
public
static
readonly
SETTING_KEY_ATTR
=
'
data-key
'
;
private
readonly
_onDidClickOverrideElement
=
new
Emitter
<
ISettingOverrideClickEvent
>
();
public
readonly
onDidClickOverrideElement
:
Event
<
ISettingOverrideClickEvent
>
=
this
.
_onDidClickOverrideElement
.
event
;
pr
ivate
readonly
_onDidChangeSetting
=
new
Emitter
<
ISettingChangeEvent
>
();
pr
otected
readonly
_onDidChangeSetting
=
new
Emitter
<
ISettingChangeEvent
>
();
public
readonly
onDidChangeSetting
:
Event
<
ISettingChangeEvent
>
=
this
.
_onDidChangeSetting
.
event
;
pr
ivate
readonly
_onDidOpenSettings
=
new
Emitter
<
string
>
();
pr
otected
readonly
_onDidOpenSettings
=
new
Emitter
<
string
>
();
public
readonly
onDidOpenSettings
:
Event
<
string
>
=
this
.
_onDidOpenSettings
.
event
;
private
readonly
_onDidClickSettingLink
=
new
Emitter
<
ISettingLinkClickEvent
>
();
...
...
@@ -365,248 +273,28 @@ export class SettingsRenderer implements ITreeRenderer {
private
readonly
_onDidFocusSetting
=
new
Emitter
<
SettingsTreeSettingElement
>
();
public
readonly
onDidFocusSetting
:
Event
<
SettingsTreeSettingElement
>
=
this
.
_onDidFocusSetting
.
event
;
private
descriptionMeasureContainer
:
HTMLElement
;
private
longestSingleLineDescription
=
0
;
private
rowHeightCache
=
new
Map
<
string
,
number
>
();
private
lastRenderedWidth
:
number
;
private
settingActions
:
IAction
[];
// Put common injections back here
constructor
(
_measureParent
:
HTMLElement
,
@
IThemeService
pr
ivate
themeService
:
IThemeService
,
@
IContextViewService
pr
ivate
contextViewService
:
IContextViewService
,
@
IOpenerService
pr
ivate
readonly
openerService
:
IOpenerService
,
@
IInstantiationService
pr
ivate
readonly
instantiationService
:
IInstantiationService
,
@
ICommandService
pr
ivate
readonly
commandService
:
ICommandService
,
@
IContextMenuService
pr
ivate
contextMenuService
:
IContextMenuService
,
@
IKeybindingService
pr
ivate
keybindingService
:
IKeybindingService
,
private
readonly
settingActions
:
IAction
[]
,
@
IThemeService
pr
otected
readonly
_
themeService
:
IThemeService
,
@
IContextViewService
pr
otected
readonly
_
contextViewService
:
IContextViewService
,
@
IOpenerService
pr
otected
readonly
_
openerService
:
IOpenerService
,
@
IInstantiationService
pr
otected
readonly
_
instantiationService
:
IInstantiationService
,
@
ICommandService
pr
otected
readonly
_
commandService
:
ICommandService
,
@
IContextMenuService
pr
otected
readonly
_
contextMenuService
:
IContextMenuService
,
@
IKeybindingService
pr
otected
readonly
_
keybindingService
:
IKeybindingService
,
)
{
this
.
descriptionMeasureContainer
=
$
(
'
.setting-item-description
'
);
DOM
.
append
(
_measureParent
,
$
(
'
.setting-measure-container.monaco-tree.settings-editor-tree
'
,
undefined
,
$
(
'
.monaco-scrollable-element
'
,
undefined
,
$
(
'
.monaco-tree-wrapper
'
,
undefined
,
$
(
'
.monaco-tree-rows
'
,
undefined
,
$
(
'
.monaco-tree-row
'
,
undefined
,
$
(
'
.setting-item
'
,
undefined
,
this
.
descriptionMeasureContainer
)))))));
this
.
settingActions
=
[
new
Action
(
'
settings.resetSetting
'
,
localize
(
'
resetSettingLabel
'
,
"
Reset Setting
"
),
undefined
,
undefined
,
(
context
:
SettingsTreeSettingElement
)
=>
{
if
(
context
)
{
this
.
_onDidChangeSetting
.
fire
({
key
:
context
.
setting
.
key
,
value
:
undefined
,
type
:
context
.
setting
.
type
as
SettingValueType
});
}
return
Promise
.
resolve
(
null
);
}),
new
Separator
(),
this
.
instantiationService
.
createInstance
(
CopySettingIdAction
),
this
.
instantiationService
.
createInstance
(
CopySettingAsJSONAction
),
];
}
showContextMenu
(
element
:
SettingsTreeSettingElement
,
settingDOMElement
:
HTMLElement
):
void
{
const
toolbarElement
:
HTMLElement
=
settingDOMElement
.
querySelector
(
'
.toolbar-toggle-more
'
);
if
(
toolbarElement
)
{
this
.
contextMenuService
.
showContextMenu
({
getActions
:
()
=>
this
.
settingActions
,
getAnchor
:
()
=>
toolbarElement
,
getActionsContext
:
()
=>
element
});
}
}
updateWidth
(
width
:
number
):
void
{
if
(
this
.
lastRenderedWidth
!==
width
)
{
this
.
rowHeightCache
=
new
Map
<
string
,
number
>
();
}
this
.
longestSingleLineDescription
=
0
;
this
.
lastRenderedWidth
=
width
;
}
getHeight
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
number
{
if
(
this
.
rowHeightCache
.
has
(
element
.
id
)
&&
!
(
element
instanceof
SettingsTreeSettingElement
&&
isExcludeSetting
(
element
.
setting
)))
{
return
this
.
rowHeightCache
.
get
(
element
.
id
);
}
const
h
=
this
.
_getHeight
(
tree
,
element
);
this
.
rowHeightCache
.
set
(
element
.
id
,
h
);
return
h
;
}
_getHeight
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
number
{
if
(
element
instanceof
SettingsTreeGroupElement
)
{
if
(
element
.
isFirstGroup
)
{
return
31
;
}
return
40
+
(
7
*
element
.
level
);
}
if
(
element
instanceof
SettingsTreeSettingElement
)
{
if
(
isExcludeSetting
(
element
.
setting
))
{
return
this
.
_getExcludeSettingHeight
(
element
);
}
else
{
return
this
.
measureSettingElementHeight
(
tree
,
element
);
}
}
if
(
element
instanceof
SettingsTreeNewExtensionsElement
)
{
return
40
;
}
return
0
;
}
_getExcludeSettingHeight
(
element
:
SettingsTreeSettingElement
):
number
{
const
displayValue
=
getExcludeDisplayValue
(
element
);
return
(
displayValue
.
length
+
1
)
*
22
+
66
+
this
.
measureSettingDescription
(
element
);
}
private
measureSettingElementHeight
(
tree
:
ITree
,
element
:
SettingsTreeSettingElement
):
number
{
let
heightExcludingDescription
=
86
;
if
(
element
.
valueType
===
'
boolean
'
)
{
heightExcludingDescription
=
60
;
}
return
heightExcludingDescription
+
this
.
measureSettingDescription
(
element
);
}
private
measureSettingDescription
(
element
:
SettingsTreeSettingElement
):
number
{
if
(
element
.
description
.
length
<
this
.
longestSingleLineDescription
*
.
8
)
{
// Most setting descriptions are one short line, so try to avoid measuring them.
// If the description is less than 80% of the longest single line description, assume this will also render to be one line.
return
18
;
}
const
boolMeasureClass
=
'
measure-bool-description
'
;
if
(
element
.
valueType
===
'
boolean
'
)
{
this
.
descriptionMeasureContainer
.
classList
.
add
(
boolMeasureClass
);
}
else
if
(
this
.
descriptionMeasureContainer
.
classList
.
contains
(
boolMeasureClass
))
{
this
.
descriptionMeasureContainer
.
classList
.
remove
(
boolMeasureClass
);
}
const
shouldRenderMarkdown
=
element
.
setting
.
descriptionIsMarkdown
&&
element
.
description
.
indexOf
(
'
\n
-
'
)
>=
0
;
while
(
this
.
descriptionMeasureContainer
.
firstChild
)
{
this
.
descriptionMeasureContainer
.
removeChild
(
this
.
descriptionMeasureContainer
.
firstChild
);
}
if
(
shouldRenderMarkdown
)
{
const
text
=
fixSettingLinks
(
element
.
description
);
const
rendered
=
renderMarkdown
({
value
:
text
});
rendered
.
classList
.
add
(
'
setting-item-description-markdown
'
);
this
.
descriptionMeasureContainer
.
appendChild
(
rendered
);
return
this
.
descriptionMeasureContainer
.
offsetHeight
;
}
else
{
// Remove markdown links, setting links, backticks
const
measureText
=
element
.
setting
.
descriptionIsMarkdown
?
fixSettingLinks
(
element
.
description
)
.
replace
(
/
\[(
.*
)\]\(
.*
\)
/g
,
'
$1
'
)
.
replace
(
/`
([^
`
]
*
)
`/g
,
'
$1
'
)
:
element
.
description
;
this
.
descriptionMeasureContainer
.
innerText
=
measureText
;
const
h
=
this
.
descriptionMeasureContainer
.
offsetHeight
;
if
(
h
<
20
&&
measureText
.
length
>
this
.
longestSingleLineDescription
)
{
this
.
longestSingleLineDescription
=
measureText
.
length
;
}
return
h
;
}
}
getTemplateId
(
tree
:
ITree
,
element
:
SettingsTreeElement
):
string
{
if
(
element
instanceof
SettingsTreeGroupElement
)
{
return
SETTINGS_GROUP_ELEMENT_TEMPLATE_ID
;
}
if
(
element
instanceof
SettingsTreeSettingElement
)
{
if
(
element
.
valueType
===
'
boolean
'
)
{
return
SETTINGS_BOOL_TEMPLATE_ID
;
}
if
(
element
.
valueType
===
'
integer
'
||
element
.
valueType
===
'
number
'
||
element
.
valueType
===
'
nullable-integer
'
||
element
.
valueType
===
'
nullable-number
'
)
{
return
SETTINGS_NUMBER_TEMPLATE_ID
;
}
if
(
element
.
valueType
===
'
string
'
)
{
return
SETTINGS_TEXT_TEMPLATE_ID
;
}
if
(
element
.
valueType
===
'
enum
'
)
{
return
SETTINGS_ENUM_TEMPLATE_ID
;
}
if
(
element
.
valueType
===
'
exclude
'
)
{
return
SETTINGS_EXCLUDE_TEMPLATE_ID
;
}
return
SETTINGS_COMPLEX_TEMPLATE_ID
;
}
if
(
element
instanceof
SettingsTreeNewExtensionsElement
)
{
return
SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID
;
}
return
''
;
}
renderTemplate
(
tree
:
ITree
,
templateId
:
string
,
container
:
HTMLElement
)
{
if
(
templateId
===
SETTINGS_GROUP_ELEMENT_TEMPLATE_ID
)
{
return
this
.
renderGroupTitleTemplate
(
container
);
}
if
(
templateId
===
SETTINGS_TEXT_TEMPLATE_ID
)
{
return
this
.
renderSettingTextTemplate
(
tree
,
container
);
}
if
(
templateId
===
SETTINGS_NUMBER_TEMPLATE_ID
)
{
return
this
.
renderSettingNumberTemplate
(
tree
,
container
);
}
if
(
templateId
===
SETTINGS_BOOL_TEMPLATE_ID
)
{
return
this
.
renderSettingBoolTemplate
(
tree
,
container
);
}
if
(
templateId
===
SETTINGS_ENUM_TEMPLATE_ID
)
{
return
this
.
renderSettingEnumTemplate
(
tree
,
container
);
}
if
(
templateId
===
SETTINGS_EXCLUDE_TEMPLATE_ID
)
{
return
this
.
renderSettingExcludeTemplate
(
tree
,
container
);
}
if
(
templateId
===
SETTINGS_COMPLEX_TEMPLATE_ID
)
{
return
this
.
renderSettingComplexTemplate
(
tree
,
container
);
}
if
(
templateId
===
SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID
)
{
return
this
.
renderNewExtensionsTemplate
(
container
);
}
return
null
;
renderTemplate
(
container
:
HTMLElement
):
any
{
throw
new
Error
(
'
to override
'
);
}
private
renderGroupTitleTemplate
(
container
:
HTMLElement
):
IGroupTitleTemplate
{
DOM
.
addClass
(
container
,
'
group-title
'
);
const
toDispose
:
IDisposable
[]
=
[];
const
template
:
IGroupTitleTemplate
=
{
parent
:
container
,
toDispose
};
return
template
;
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
any
):
void
{
throw
new
Error
(
'
to override
'
);
}
pr
ivate
renderCommonTemplate
(
tree
:
ITree
,
container
:
HTMLElement
,
typeClass
:
string
):
ISettingItemTemplate
{
pr
otected
renderCommonTemplate
(
tree
:
any
,
container
:
HTMLElement
,
typeClass
:
string
):
ISettingItemTemplate
{
DOM
.
addClass
(
container
,
'
setting-item
'
);
DOM
.
addClass
(
container
,
'
setting-item-
'
+
typeClass
);
const
titleElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-title
'
));
...
...
@@ -647,17 +335,10 @@ export class SettingsRenderer implements ITreeRenderer {
toDispose
.
push
(
DOM
.
addDisposableListener
(
titleElement
,
DOM
.
EventType
.
MOUSE_ENTER
,
e
=>
container
.
classList
.
add
(
'
mouseover
'
)));
toDispose
.
push
(
DOM
.
addDisposableListener
(
titleElement
,
DOM
.
EventType
.
MOUSE_LEAVE
,
e
=>
container
.
classList
.
remove
(
'
mouseover
'
)));
toDispose
.
push
(
DOM
.
addStandardDisposableListener
(
valueElement
,
'
keydown
'
,
(
e
:
StandardKeyboardEvent
)
=>
{
if
(
e
.
keyCode
===
KeyCode
.
Escape
)
{
tree
.
domFocus
();
e
.
browserEvent
.
stopPropagation
();
}
}));
return
template
;
}
pr
ivate
addSettingElementFocusHandler
(
template
:
ISettingItemTemplate
):
void
{
pr
otected
addSettingElementFocusHandler
(
template
:
ISettingItemTemplate
):
void
{
const
focusTracker
=
DOM
.
trackFocus
(
template
.
containerElement
);
template
.
toDispose
.
push
(
focusTracker
);
focusTracker
.
onDidBlur
(()
=>
{
...
...
@@ -675,76 +356,14 @@ export class SettingsRenderer implements ITreeRenderer {
});
}
private
renderSettingTextTemplate
(
tree
:
ITree
,
container
:
HTMLElement
,
type
=
'
text
'
):
ISettingTextItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
tree
,
container
,
'
text
'
);
const
validationErrorMessageElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-validation-message
'
));
const
inputBox
=
new
InputBox
(
common
.
controlElement
,
this
.
contextViewService
);
common
.
toDispose
.
push
(
inputBox
);
common
.
toDispose
.
push
(
attachInputBoxStyler
(
inputBox
,
this
.
themeService
,
{
inputBackground
:
settingsTextInputBackground
,
inputForeground
:
settingsTextInputForeground
,
inputBorder
:
settingsTextInputBorder
}));
common
.
toDispose
.
push
(
inputBox
.
onDidChange
(
e
=>
{
if
(
template
.
onChange
)
{
template
.
onChange
(
e
);
}
}));
common
.
toDispose
.
push
(
inputBox
);
inputBox
.
inputElement
.
classList
.
add
(
SettingsRenderer
.
CONTROL_CLASS
);
const
template
:
ISettingTextItemTemplate
=
{
...
common
,
inputBox
,
validationErrorMessageElement
};
this
.
addSettingElementFocusHandler
(
template
);
return
template
;
}
private
renderSettingNumberTemplate
(
tree
:
ITree
,
container
:
HTMLElement
):
ISettingNumberItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
tree
,
container
,
'
number
'
);
const
validationErrorMessageElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-validation-message
'
));
const
inputBox
=
new
InputBox
(
common
.
controlElement
,
this
.
contextViewService
,
{
type
:
'
number
'
});
common
.
toDispose
.
push
(
inputBox
);
common
.
toDispose
.
push
(
attachInputBoxStyler
(
inputBox
,
this
.
themeService
,
{
inputBackground
:
settingsNumberInputBackground
,
inputForeground
:
settingsNumberInputForeground
,
inputBorder
:
settingsNumberInputBorder
}));
common
.
toDispose
.
push
(
inputBox
.
onDidChange
(
e
=>
{
if
(
template
.
onChange
)
{
template
.
onChange
(
e
);
}
}));
common
.
toDispose
.
push
(
inputBox
);
inputBox
.
inputElement
.
classList
.
add
(
SettingsRenderer
.
CONTROL_CLASS
);
const
template
:
ISettingNumberItemTemplate
=
{
...
common
,
inputBox
,
validationErrorMessageElement
};
this
.
addSettingElementFocusHandler
(
template
);
return
template
;
}
private
renderSettingToolbar
(
container
:
HTMLElement
):
ToolBar
{
const
toggleMenuKeybinding
=
this
.
keybindingService
.
lookupKeybinding
(
SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU
);
protected
renderSettingToolbar
(
container
:
HTMLElement
):
ToolBar
{
const
toggleMenuKeybinding
=
this
.
_keybindingService
.
lookupKeybinding
(
SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU
);
let
toggleMenuTitle
=
localize
(
'
settingsContextMenuTitle
'
,
"
More Actions...
"
);
if
(
toggleMenuKeybinding
)
{
toggleMenuTitle
+=
` (
${
toggleMenuKeybinding
&&
toggleMenuKeybinding
.
getLabel
()}
)`
;
}
const
toolbar
=
new
ToolBar
(
container
,
this
.
contextMenuService
,
{
const
toolbar
=
new
ToolBar
(
container
,
this
.
_
contextMenuService
,
{
toggleMenuTitle
});
toolbar
.
setActions
([],
this
.
settingActions
)();
...
...
@@ -756,95 +375,418 @@ export class SettingsRenderer implements ITreeRenderer {
return
toolbar
;
}
private
renderSettingBoolTemplate
(
tree
:
ITree
,
container
:
HTMLElement
):
ISettingBoolItemTemplate
{
DOM
.
addClass
(
container
,
'
setting-item
'
);
DOM
.
addClass
(
container
,
'
setting-item-bool
'
);
protected
renderSettingElement
(
node
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
template
:
ISettingItemTemplate
|
ISettingBoolItemTemplate
):
void
{
const
element
=
node
.
element
;
template
.
context
=
element
;
template
.
toolbar
.
context
=
element
;
const
titleElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-title
'
));
const
categoryElement
=
DOM
.
append
(
titleElement
,
$
(
'
span.setting-item-category
'
));
const
labelElement
=
DOM
.
append
(
titleElement
,
$
(
'
span.setting-item-label
'
));
const
otherOverridesElement
=
DOM
.
append
(
titleElement
,
$
(
'
span.setting-item-overrides
'
));
const
setting
=
element
.
setting
;
const
descriptionAndValueElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-value-description
'
));
const
controlElement
=
DOM
.
append
(
descriptionAndValueElement
,
$
(
'
.setting-item-bool-control
'
));
const
descriptionElement
=
DOM
.
append
(
descriptionAndValueElement
,
$
(
'
.setting-item-description
'
));
const
modifiedIndicatorElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-modified-indicator
'
));
modifiedIndicatorElement
.
title
=
localize
(
'
modified
'
,
"
Modified
"
);
DOM
.
toggleClass
(
template
.
containerElement
,
'
is-configured
'
,
element
.
isConfigured
);
DOM
.
toggleClass
(
template
.
containerElement
,
'
is-expanded
'
,
true
);
template
.
containerElement
.
setAttribute
(
AbstractSettingRenderer
.
SETTING_KEY_ATTR
,
element
.
setting
.
key
);
const
titleTooltip
=
setting
.
key
+
(
element
.
isConfigured
?
'
- Modified
'
:
''
);
template
.
categoryElement
.
textContent
=
element
.
displayCategory
&&
(
element
.
displayCategory
+
'
:
'
);
template
.
categoryElement
.
title
=
titleTooltip
;
template
.
labelElement
.
textContent
=
element
.
displayLabel
;
template
.
labelElement
.
title
=
titleTooltip
;
const
deprecationWarningElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-deprecation-message
'
));
template
.
descriptionElement
.
innerHTML
=
''
;
if
(
element
.
setting
.
descriptionIsMarkdown
)
{
const
renderedDescription
=
this
.
renderDescriptionMarkdown
(
element
,
element
.
description
,
template
.
toDispose
);
template
.
descriptionElement
.
appendChild
(
renderedDescription
);
}
else
{
template
.
descriptionElement
.
innerText
=
element
.
description
;
}
const
baseId
=
(
element
.
displayCategory
+
'
_
'
+
element
.
displayLabel
).
replace
(
/ /g
,
'
_
'
).
toLowerCase
();
template
.
descriptionElement
.
id
=
baseId
+
'
_setting_description
'
;
template
.
otherOverridesElement
.
innerHTML
=
''
;
if
(
element
.
overriddenScopeList
.
length
)
{
const
otherOverridesLabel
=
element
.
isConfigured
?
localize
(
'
alsoConfiguredIn
'
,
"
Also modified in
"
)
:
localize
(
'
configuredIn
'
,
"
Modified in
"
);
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
span
'
,
null
,
`(
${
otherOverridesLabel
}
: `
));
for
(
let
i
=
0
;
i
<
element
.
overriddenScopeList
.
length
;
i
++
)
{
let
view
=
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
a.modified-scope
'
,
null
,
element
.
overriddenScopeList
[
i
]));
if
(
i
!==
element
.
overriddenScopeList
.
length
-
1
)
{
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
span
'
,
null
,
'
,
'
));
}
else
{
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
span
'
,
null
,
'
)
'
));
}
DOM
.
addStandardDisposableListener
(
view
,
DOM
.
EventType
.
CLICK
,
(
e
:
IMouseEvent
)
=>
{
this
.
_onDidClickOverrideElement
.
fire
({
targetKey
:
element
.
setting
.
key
,
scope
:
element
.
overriddenScopeList
[
i
]
});
e
.
preventDefault
();
e
.
stopPropagation
();
});
}
}
const
onChange
=
value
=>
this
.
_onDidChangeSetting
.
fire
({
key
:
element
.
setting
.
key
,
value
,
type
:
template
.
context
.
valueType
});
template
.
deprecationWarningElement
.
innerText
=
element
.
setting
.
deprecationMessage
||
''
;
this
.
renderValue
(
element
,
<
ISettingItemTemplate
>
template
,
onChange
);
// Remove tree attributes - sometimes overridden by tree - should be managed there
template
.
containerElement
.
parentElement
.
parentElement
.
removeAttribute
(
'
role
'
);
template
.
containerElement
.
parentElement
.
parentElement
.
removeAttribute
(
'
aria-level
'
);
template
.
containerElement
.
parentElement
.
parentElement
.
removeAttribute
(
'
aria-posinset
'
);
template
.
containerElement
.
parentElement
.
parentElement
.
removeAttribute
(
'
aria-setsize
'
);
}
private
renderDescriptionMarkdown
(
element
:
SettingsTreeSettingElement
,
text
:
string
,
disposeables
:
IDisposable
[]):
HTMLElement
{
// Rewrite `#editor.fontSize#` to link format
text
=
fixSettingLinks
(
text
);
const
renderedMarkdown
=
renderMarkdown
({
value
:
text
},
{
actionHandler
:
{
callback
:
(
content
:
string
)
=>
{
if
(
startsWith
(
content
,
'
#
'
))
{
const
e
:
ISettingLinkClickEvent
=
{
source
:
element
,
targetKey
:
content
.
substr
(
1
)
};
this
.
_onDidClickSettingLink
.
fire
(
e
);
}
else
{
let
uri
:
URI
;
try
{
uri
=
URI
.
parse
(
content
);
}
catch
(
err
)
{
// ignore
}
if
(
uri
)
{
this
.
_openerService
.
open
(
uri
).
catch
(
onUnexpectedError
);
}
}
},
disposeables
}
});
renderedMarkdown
.
classList
.
add
(
'
setting-item-description-markdown
'
);
cleanRenderedMarkdown
(
renderedMarkdown
);
return
renderedMarkdown
;
}
protected
abstract
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingItemTemplate
,
onChange
:
(
value
:
any
)
=>
void
):
void
;
protected
setElementAriaLabels
(
dataElement
:
SettingsTreeSettingElement
,
templateId
:
string
,
template
:
ISettingItemTemplate
):
string
{
// Create base Id for element references
const
baseId
=
(
dataElement
.
displayCategory
+
'
_
'
+
dataElement
.
displayLabel
).
replace
(
/ /g
,
'
_
'
).
toLowerCase
();
const
modifiedText
=
template
.
otherOverridesElement
.
textContent
?
template
.
otherOverridesElement
.
textContent
:
(
dataElement
.
isConfigured
?
localize
(
'
settings.Modified
'
,
'
Modified.
'
)
:
''
);
let
itemElement
=
null
;
// Use '.' as reader pause
let
label
=
dataElement
.
displayCategory
+
'
'
+
dataElement
.
displayLabel
+
'
.
'
;
// Setup and add ARIA attributes
// Create id and label for control/input element - parent is wrapper div
if
(
templateId
===
SETTINGS_TEXT_TEMPLATE_ID
)
{
if
(
itemElement
=
(
<
ISettingTextItemTemplate
>
template
).
inputBox
.
inputElement
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
textbox
'
);
label
+=
modifiedText
;
}
}
else
if
(
templateId
===
SETTINGS_NUMBER_TEMPLATE_ID
)
{
if
(
itemElement
=
(
<
ISettingNumberItemTemplate
>
template
).
inputBox
.
inputElement
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
textbox
'
);
label
+=
'
number.
'
+
modifiedText
;
}
}
else
if
(
templateId
===
SETTINGS_BOOL_TEMPLATE_ID
)
{
if
(
itemElement
=
(
<
ISettingBoolItemTemplate
>
template
).
checkbox
.
domNode
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
checkbox
'
);
label
+=
modifiedText
;
// Add checkbox target to description clickable and able to toggle checkbox
template
.
descriptionElement
.
setAttribute
(
'
checkbox_label_target_id
'
,
baseId
+
'
_setting_item
'
);
}
}
else
if
(
templateId
===
SETTINGS_ENUM_TEMPLATE_ID
)
{
if
(
itemElement
=
template
.
controlElement
.
firstElementChild
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
combobox
'
);
label
+=
modifiedText
;
}
}
else
{
// Don't change attributes if we don't know what we areFunctions
return
''
;
}
// We don't have control element, return empty label
if
(
!
itemElement
)
{
return
''
;
}
// Labels will not be read on descendent input elements of the parent treeitem
// unless defined as roles for input items
// voiceover does not seem to use labeledby correctly, set labels directly on input elements
itemElement
.
id
=
baseId
+
'
_setting_item
'
;
itemElement
.
setAttribute
(
'
aria-label
'
,
label
);
itemElement
.
setAttribute
(
'
aria-describedby
'
,
baseId
+
'
_setting_description settings_aria_more_actions_shortcut_label
'
);
return
label
;
}
disposeTemplate
(
template
:
IDisposableTemplate
):
void
{
dispose
(
template
.
toDispose
);
}
}
export
class
SettingGroupRenderer
implements
ITreeRenderer
<
SettingsTreeGroupElement
,
never
,
IGroupTitleTemplate
>
{
templateId
=
SETTINGS_ELEMENT_TEMPLATE_ID
;
renderTemplate
(
container
:
HTMLElement
):
IGroupTitleTemplate
{
DOM
.
addClass
(
container
,
'
group-title
'
);
const
toDispose
:
IDisposable
[]
=
[];
const
checkbox
=
new
Checkbox
({
actionClassName
:
'
setting-value-checkbox
'
,
isChecked
:
true
,
title
:
''
,
inputActiveOptionBorder
:
null
});
controlElement
.
appendChild
(
checkbox
.
domNode
);
toDispose
.
push
(
checkbox
);
toDispose
.
push
(
checkbox
.
onChange
(()
=>
{
if
(
template
.
onChange
)
{
template
.
onChange
(
checkbox
.
checked
);
const
template
:
IGroupTitleTemplate
=
{
parent
:
container
,
toDispose
};
return
template
;
}
renderElement
(
element
:
ITreeNode
<
SettingsTreeGroupElement
,
never
>
,
index
:
number
,
templateData
:
IGroupTitleTemplate
):
void
{
templateData
.
parent
.
innerHTML
=
''
;
const
labelElement
=
DOM
.
append
(
templateData
.
parent
,
$
(
'
div.settings-group-title-label
'
));
labelElement
.
classList
.
add
(
`settings-group-level-
${
element
.
element
.
level
}
`
);
labelElement
.
textContent
=
element
.
element
.
label
;
if
(
element
.
element
.
isFirstGroup
)
{
labelElement
.
classList
.
add
(
'
settings-group-first
'
);
}
}
disposeTemplate
(
templateData
:
IGroupTitleTemplate
):
void
{
}
}
export
class
SettingNewExtensionsRenderer
implements
ITreeRenderer
<
SettingsTreeNewExtensionsElement
,
never
,
ISettingNewExtensionsTemplate
>
{
templateId
=
SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID
;
constructor
(
@
IThemeService
private
_themeService
:
IThemeService
,
@
ICommandService
private
_commandService
:
ICommandService
,
)
{
}
renderTemplate
(
container
:
HTMLElement
):
ISettingNewExtensionsTemplate
{
const
toDispose
:
IDisposable
[]
=
[];
container
.
classList
.
add
(
'
setting-item-new-extensions
'
);
const
button
=
new
Button
(
container
,
{
title
:
true
,
buttonBackground
:
null
,
buttonHoverBackground
:
null
});
toDispose
.
push
(
button
);
toDispose
.
push
(
button
.
onDidClick
(()
=>
{
if
(
template
.
context
)
{
this
.
_commandService
.
executeCommand
(
'
workbench.extensions.action.showExtensionsWithIds
'
,
template
.
context
.
extensionIds
);
}
}));
button
.
label
=
localize
(
'
newExtensionsButtonLabel
'
,
"
Show matching extensions
"
);
button
.
element
.
classList
.
add
(
'
settings-new-extensions-button
'
);
toDispose
.
push
(
attachButtonStyler
(
button
,
this
.
_themeService
));
// Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety
// Also have to ignore embedded links - too buried to stop propagation
toDispose
.
push
(
DOM
.
addDisposableListener
(
descriptionElement
,
DOM
.
EventType
.
MOUSE_DOWN
,
(
e
)
=>
{
const
targetElement
=
<
HTMLElement
>
e
.
target
;
const
targetId
=
descriptionElement
.
getAttribute
(
'
checkbox_label_target_id
'
);
const
template
:
ISettingNewExtensionsTemplate
=
{
button
,
toDispose
};
// Make sure we are not a link and the target ID matches
// Toggle target checkbox
if
(
targetElement
.
tagName
.
toLowerCase
()
!==
'
a
'
&&
targetId
===
template
.
checkbox
.
domNode
.
id
)
{
template
.
checkbox
.
checked
=
template
.
checkbox
.
checked
?
false
:
true
;
template
.
onChange
(
checkbox
.
checked
);
}
DOM
.
EventHelper
.
stop
(
e
);
return
template
;
}
renderElement
(
element
:
ITreeNode
<
SettingsTreeNewExtensionsElement
,
never
>
,
index
:
number
,
templateData
:
ISettingNewExtensionsTemplate
):
void
{
templateData
.
context
=
element
.
element
;
}
disposeTemplate
(
template
:
IDisposableTemplate
):
void
{
dispose
(
template
.
toDispose
);
}
}
export
class
SettingComplexRenderer
extends
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeSettingElement
,
never
,
ISettingComplexItemTemplate
>
{
templateId
=
SETTINGS_COMPLEX_TEMPLATE_ID
;
renderTemplate
(
container
:
HTMLElement
):
ISettingComplexItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
null
,
container
,
'
complex
'
);
const
openSettingsButton
=
new
Button
(
common
.
controlElement
,
{
title
:
true
,
buttonBackground
:
null
,
buttonHoverBackground
:
null
});
common
.
toDispose
.
push
(
openSettingsButton
);
common
.
toDispose
.
push
(
openSettingsButton
.
onDidClick
(()
=>
template
.
onChange
(
null
)));
openSettingsButton
.
label
=
localize
(
'
editInSettingsJson
'
,
"
Edit in settings.json
"
);
openSettingsButton
.
element
.
classList
.
add
(
'
edit-in-settings-button
'
);
common
.
toDispose
.
push
(
attachButtonStyler
(
openSettingsButton
,
this
.
_themeService
,
{
buttonBackground
:
Color
.
transparent
.
toString
(),
buttonHoverBackground
:
Color
.
transparent
.
toString
(),
buttonForeground
:
'
foreground
'
}));
const
template
:
ISettingComplexItemTemplate
=
{
...
common
,
button
:
openSettingsButton
};
checkbox
.
domNode
.
classList
.
add
(
SettingsRenderer
.
CONTROL_CLASS
);
const
toolbarContainer
=
DOM
.
append
(
container
,
$
(
'
.setting-toolbar-container
'
));
const
toolbar
=
this
.
renderSettingToolbar
(
toolbarContainer
);
toDispose
.
push
(
toolbar
);
this
.
addSettingElementFocusHandler
(
template
);
const
template
:
ISettingBoolItemTemplate
=
{
toDispose
,
return
template
;
}
containerElement
:
container
,
categoryElement
,
labelElement
,
controlElement
,
checkbox
,
descriptionElement
,
deprecationWarningElement
,
otherOverridesElement
,
toolbar
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
ISettingComplexItemTemplate
):
void
{
super
.
renderSettingElement
(
element
,
index
,
templateData
);
}
protected
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingExcludeItemTemplate
,
onChange
:
(
value
:
string
)
=>
void
):
void
{
template
.
onChange
=
()
=>
this
.
_onDidOpenSettings
.
fire
(
dataElement
.
setting
.
key
);
}
}
export
class
SettingExcludeRenderer
extends
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeSettingElement
,
never
,
ISettingExcludeItemTemplate
>
{
templateId
=
SETTINGS_EXCLUDE_TEMPLATE_ID
;
renderTemplate
(
container
:
HTMLElement
):
ISettingExcludeItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
null
,
container
,
'
exclude
'
);
const
excludeWidget
=
this
.
_instantiationService
.
createInstance
(
ExcludeSettingWidget
,
common
.
controlElement
);
excludeWidget
.
domNode
.
classList
.
add
(
AbstractSettingRenderer
.
CONTROL_CLASS
);
common
.
toDispose
.
push
(
excludeWidget
);
const
template
:
ISettingExcludeItemTemplate
=
{
...
common
,
excludeWidget
};
this
.
addSettingElementFocusHandler
(
template
);
// Prevent clicks from being handled by list
toDispose
.
push
(
DOM
.
addDisposableListener
(
controlElement
,
'
mousedown
'
,
e
=>
e
.
stopPropagation
()));
common
.
toDispose
.
push
(
excludeWidget
.
onDidChangeExclude
(
e
=>
this
.
onDidChangeExclude
(
template
,
e
)));
return
template
;
}
private
onDidChangeExclude
(
template
:
ISettingExcludeItemTemplate
,
e
:
IExcludeChangeEvent
):
void
{
if
(
template
.
context
)
{
const
newValue
=
{
...
template
.
context
.
scopeValue
};
// first delete the existing entry, if present
if
(
e
.
originalPattern
)
{
if
(
e
.
originalPattern
in
template
.
context
.
defaultValue
)
{
// delete a default by overriding it
newValue
[
e
.
originalPattern
]
=
false
;
}
else
{
delete
newValue
[
e
.
originalPattern
];
}
}
// then add the new or updated entry, if present
if
(
e
.
pattern
)
{
if
(
e
.
pattern
in
template
.
context
.
defaultValue
&&
!
e
.
sibling
)
{
// add a default by deleting its override
delete
newValue
[
e
.
pattern
];
}
else
{
newValue
[
e
.
pattern
]
=
e
.
sibling
?
{
when
:
e
.
sibling
}
:
true
;
}
}
const
sortKeys
=
(
obj
)
=>
{
const
keyArray
=
Object
.
keys
(
obj
)
.
map
(
key
=>
({
key
,
val
:
obj
[
key
]
}))
.
sort
((
a
,
b
)
=>
a
.
key
.
localeCompare
(
b
.
key
));
const
retVal
=
{};
keyArray
.
forEach
(
pair
=>
{
retVal
[
pair
.
key
]
=
pair
.
val
;
});
return
retVal
;
};
this
.
_onDidChangeSetting
.
fire
({
key
:
template
.
context
.
setting
.
key
,
value
:
Object
.
keys
(
newValue
).
length
===
0
?
undefined
:
sortKeys
(
newValue
),
type
:
template
.
context
.
valueType
});
}
}
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
ISettingExcludeItemTemplate
):
void
{
super
.
renderSettingElement
(
element
,
index
,
templateData
);
}
protected
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingExcludeItemTemplate
,
onChange
:
(
value
:
string
)
=>
void
):
void
{
const
value
=
getExcludeDisplayValue
(
dataElement
);
template
.
excludeWidget
.
setValue
(
value
);
template
.
context
=
dataElement
;
}
}
export
class
SettingTextRenderer
extends
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeSettingElement
,
never
,
ISettingTextItemTemplate
>
{
templateId
=
SETTINGS_TEXT_TEMPLATE_ID
;
renderTemplate
(
container
:
HTMLElement
):
ISettingTextItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
null
,
container
,
'
text
'
);
const
validationErrorMessageElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-validation-message
'
));
const
inputBox
=
new
InputBox
(
common
.
controlElement
,
this
.
_contextViewService
);
common
.
toDispose
.
push
(
inputBox
);
common
.
toDispose
.
push
(
attachInputBoxStyler
(
inputBox
,
this
.
_themeService
,
{
inputBackground
:
settingsTextInputBackground
,
inputForeground
:
settingsTextInputForeground
,
inputBorder
:
settingsTextInputBorder
}));
common
.
toDispose
.
push
(
inputBox
.
onDidChange
(
e
=>
{
if
(
template
.
onChange
)
{
template
.
onChange
(
e
);
}
}));
common
.
toDispose
.
push
(
inputBox
);
inputBox
.
inputElement
.
classList
.
add
(
AbstractSettingRenderer
.
CONTROL_CLASS
);
const
template
:
ISettingTextItemTemplate
=
{
...
common
,
inputBox
,
validationErrorMessageElement
};
toDispose
.
push
(
DOM
.
addStandardDisposableListener
(
controlElement
,
'
keydown
'
,
(
e
:
StandardKeyboardEvent
)
=>
{
if
(
e
.
keyCode
===
KeyCode
.
Escape
)
{
tree
.
domFocus
();
e
.
browserEvent
.
stopPropagation
();
}
}));
this
.
addSettingElementFocusHandler
(
template
);
return
template
;
}
public
cancelSuggesters
()
{
this
.
contextViewService
.
hideContextView
();
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
ISettingTextItemTemplate
):
void
{
super
.
renderSettingElement
(
element
,
index
,
templateData
);
}
protected
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingTextItemTemplate
,
onChange
:
(
value
:
string
)
=>
void
):
void
{
const
label
=
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_TEXT_TEMPLATE_ID
,
template
);
template
.
onChange
=
null
;
template
.
inputBox
.
value
=
dataElement
.
value
;
template
.
onChange
=
value
=>
{
renderValidations
(
dataElement
,
template
,
false
,
label
);
onChange
(
value
);
};
renderValidations
(
dataElement
,
template
,
true
,
label
);
}
}
export
class
SettingEnumRenderer
extends
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeSettingElement
,
never
,
ISettingEnumItemTemplate
>
{
templateId
=
SETTINGS_ENUM_TEMPLATE_ID
;
private
renderSettingEnumTemplate
(
tree
:
ITree
,
container
:
HTMLElement
):
ISettingEnumItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
tree
,
container
,
'
enum
'
);
renderTemplate
(
container
:
HTMLElement
):
ISettingEnumItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
null
,
container
,
'
enum
'
);
const
selectBox
=
new
SelectBox
([],
undefined
,
this
.
contextViewService
,
undefined
,
{
useCustomDrawn
:
true
});
const
selectBox
=
new
SelectBox
([],
undefined
,
this
.
_
contextViewService
,
undefined
,
{
useCustomDrawn
:
true
});
common
.
toDispose
.
push
(
selectBox
);
common
.
toDispose
.
push
(
attachSelectBoxStyler
(
selectBox
,
this
.
themeService
,
{
common
.
toDispose
.
push
(
attachSelectBoxStyler
(
selectBox
,
this
.
_
themeService
,
{
selectBackground
:
settingsSelectBackground
,
selectForeground
:
settingsSelectForeground
,
selectBorder
:
settingsSelectBorder
,
...
...
@@ -853,7 +795,7 @@ export class SettingsRenderer implements ITreeRenderer {
selectBox
.
render
(
common
.
controlElement
);
const
selectElement
=
common
.
controlElement
.
querySelector
(
'
select
'
);
if
(
selectElement
)
{
selectElement
.
classList
.
add
(
Settings
Renderer
.
CONTROL_CLASS
);
selectElement
.
classList
.
add
(
AbstractSetting
Renderer
.
CONTROL_CLASS
);
}
common
.
toDispose
.
push
(
...
...
@@ -876,85 +818,65 @@ export class SettingsRenderer implements ITreeRenderer {
return
template
;
}
private
renderSettingExcludeTemplate
(
tree
:
ITree
,
container
:
HTMLElement
):
ISettingExcludeItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
tree
,
container
,
'
exclude
'
);
const
excludeWidget
=
this
.
instantiationService
.
createInstance
(
ExcludeSettingWidget
,
common
.
controlElement
);
excludeWidget
.
domNode
.
classList
.
add
(
SettingsRenderer
.
CONTROL_CLASS
);
common
.
toDispose
.
push
(
excludeWidget
);
const
template
:
ISettingExcludeItemTemplate
=
{
...
common
,
excludeWidget
};
this
.
addSettingElementFocusHandler
(
template
);
common
.
toDispose
.
push
(
excludeWidget
.
onDidChangeExclude
(
e
=>
{
if
(
template
.
context
)
{
const
newValue
=
{
...
template
.
context
.
scopeValue
};
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
ISettingEnumItemTemplate
):
void
{
super
.
renderSettingElement
(
element
,
index
,
templateData
);
}
// first delete the existing entry, if present
if
(
e
.
originalPattern
)
{
if
(
e
.
originalPattern
in
template
.
context
.
defaultValue
)
{
// delete a default by overriding it
newValue
[
e
.
originalPattern
]
=
false
;
}
else
{
delete
newValue
[
e
.
originalPattern
];
}
}
protected
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingEnumItemTemplate
,
onChange
:
(
value
:
string
)
=>
void
):
void
{
const
enumDescriptions
=
dataElement
.
setting
.
enumDescriptions
;
const
enumDescriptionsAreMarkdown
=
dataElement
.
setting
.
enumDescriptionsAreMarkdown
;
// then add the new or updated entry, if present
if
(
e
.
pattern
)
{
if
(
e
.
pattern
in
template
.
context
.
defaultValue
&&
!
e
.
sibling
)
{
// add a default by deleting its override
delete
newValue
[
e
.
pattern
];
}
else
{
newValue
[
e
.
pattern
]
=
e
.
sibling
?
{
when
:
e
.
sibling
}
:
true
;
}
}
let
displayOptions
=
dataElement
.
setting
.
enum
.
map
(
String
)
.
map
(
escapeInvisibleChars
)
.
map
((
data
,
index
)
=>
<
ISelectOptionItem
>
{
text
:
data
,
description
:
(
enumDescriptions
&&
enumDescriptions
[
index
]
&&
(
enumDescriptionsAreMarkdown
?
fixSettingLinks
(
enumDescriptions
[
index
],
false
)
:
enumDescriptions
[
index
])),
descriptionIsMarkdown
:
enumDescriptionsAreMarkdown
,
decoratorRight
:
(
data
===
dataElement
.
defaultValue
?
localize
(
'
settings.Default
'
,
"
{0}
"
,
'
default
'
)
:
''
)
});
const
sortKeys
=
(
obj
)
=>
{
const
keyArray
=
Object
.
keys
(
obj
)
.
map
(
key
=>
({
key
,
val
:
obj
[
key
]
}))
.
sort
((
a
,
b
)
=>
a
.
key
.
localeCompare
(
b
.
key
));
template
.
selectBox
.
setOptions
(
displayOptions
);
const
retVal
=
{};
keyArray
.
forEach
(
pair
=>
{
retVal
[
pair
.
key
]
=
pair
.
val
;
});
return
retVal
;
};
const
label
=
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_ENUM_TEMPLATE_ID
,
template
);
template
.
selectBox
.
setAriaLabel
(
label
);
this
.
_onDidChangeSetting
.
fire
({
key
:
template
.
context
.
setting
.
key
,
value
:
Object
.
keys
(
newValue
).
length
===
0
?
undefined
:
sortKeys
(
newValue
),
type
:
template
.
context
.
valueType
});
}
}));
const
idx
=
dataElement
.
setting
.
enum
.
indexOf
(
dataElement
.
value
);
template
.
onChange
=
null
;
template
.
selectBox
.
select
(
idx
);
template
.
onChange
=
idx
=>
onChange
(
dataElement
.
setting
.
enum
[
idx
]);
return
template
;
template
.
enumDescriptionElement
.
innerHTML
=
''
;
}
}
private
renderSettingComplexTemplate
(
tree
:
ITree
,
container
:
HTMLElement
):
ISettingComplexItemTemplate
{
const
common
=
this
.
renderCommonTemplate
(
tree
,
container
,
'
complex
'
)
;
export
class
SettingNumberRenderer
extends
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeSettingElement
,
never
,
ISettingNumberItemTemplate
>
{
templateId
=
SETTINGS_NUMBER_TEMPLATE_ID
;
const
openSettingsButton
=
new
Button
(
common
.
controlElement
,
{
title
:
true
,
buttonBackground
:
null
,
buttonHoverBackground
:
null
});
common
.
toDispose
.
push
(
openSettingsButton
);
common
.
toDispose
.
push
(
openSettingsButton
.
onDidClick
(()
=>
template
.
onChange
(
null
)));
openSettingsButton
.
label
=
localize
(
'
editInSettingsJson
'
,
"
Edit in settings.json
"
);
openSettingsButton
.
element
.
classList
.
add
(
'
edit-in-settings-button
'
);
renderTemplate
(
container
:
HTMLElement
):
ISettingNumberItemTemplate
{
const
common
=
super
.
renderCommonTemplate
(
null
,
container
,
'
number
'
);
const
validationErrorMessageElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-validation-message
'
));
common
.
toDispose
.
push
(
attachButtonStyler
(
openSettingsButton
,
this
.
themeService
,
{
buttonBackground
:
Color
.
transparent
.
toString
(),
buttonHoverBackground
:
Color
.
transparent
.
toString
(),
buttonForeground
:
'
foreground
'
const
inputBox
=
new
InputBox
(
common
.
controlElement
,
this
.
_contextViewService
,
{
type
:
'
number
'
});
common
.
toDispose
.
push
(
inputBox
);
common
.
toDispose
.
push
(
attachInputBoxStyler
(
inputBox
,
this
.
_themeService
,
{
inputBackground
:
settingsNumberInputBackground
,
inputForeground
:
settingsNumberInputForeground
,
inputBorder
:
settingsNumberInputBorder
}));
common
.
toDispose
.
push
(
inputBox
.
onDidChange
(
e
=>
{
if
(
template
.
onChange
)
{
template
.
onChange
(
e
);
}
}));
common
.
toDispose
.
push
(
inputBox
);
inputBox
.
inputElement
.
classList
.
add
(
AbstractSettingRenderer
.
CONTROL_CLASS
);
const
template
:
ISetting
Complex
ItemTemplate
=
{
const
template
:
ISetting
Number
ItemTemplate
=
{
...
common
,
button
:
openSettingsButton
inputBox
,
validationErrorMessageElement
};
this
.
addSettingElementFocusHandler
(
template
);
...
...
@@ -962,198 +884,112 @@ export class SettingsRenderer implements ITreeRenderer {
return
template
;
}
private
renderNewExtensionsTemplate
(
container
:
HTMLElement
):
ISettingNewExtensionsTemplate
{
const
toDispose
:
IDisposable
[]
=
[];
container
.
classList
.
add
(
'
setting-item-new-extensions
'
);
const
button
=
new
Button
(
container
,
{
title
:
true
,
buttonBackground
:
null
,
buttonHoverBackground
:
null
});
toDispose
.
push
(
button
);
toDispose
.
push
(
button
.
onDidClick
(()
=>
{
if
(
template
.
context
)
{
this
.
commandService
.
executeCommand
(
'
workbench.extensions.action.showExtensionsWithIds
'
,
template
.
context
.
extensionIds
);
}
}));
button
.
label
=
localize
(
'
newExtensionsButtonLabel
'
,
"
Show matching extensions
"
);
button
.
element
.
classList
.
add
(
'
settings-new-extensions-button
'
);
toDispose
.
push
(
attachButtonStyler
(
button
,
this
.
themeService
));
const
template
:
ISettingNewExtensionsTemplate
=
{
button
,
toDispose
};
// this.addSettingElementFocusHandler(template);
return
template
;
}
renderElement
(
tree
:
ITree
,
element
:
SettingsTreeElement
,
templateId
:
string
,
template
:
any
):
void
{
if
(
templateId
===
SETTINGS_GROUP_ELEMENT_TEMPLATE_ID
)
{
return
this
.
renderGroupElement
(
<
SettingsTreeGroupElement
>
element
,
template
);
}
if
(
templateId
===
SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID
)
{
return
this
.
renderNewExtensionsElement
(
<
SettingsTreeNewExtensionsElement
>
element
,
template
);
}
return
this
.
renderSettingElement
(
tree
,
<
SettingsTreeSettingElement
>
element
,
templateId
,
template
);
}
private
renderGroupElement
(
element
:
SettingsTreeGroupElement
,
template
:
IGroupTitleTemplate
):
void
{
template
.
parent
.
innerHTML
=
''
;
const
labelElement
=
DOM
.
append
(
template
.
parent
,
$
(
'
div.settings-group-title-label
'
));
labelElement
.
classList
.
add
(
`settings-group-level-
${
element
.
level
}
`
);
labelElement
.
textContent
=
(
<
SettingsTreeGroupElement
>
element
).
label
;
if
(
element
.
isFirstGroup
)
{
labelElement
.
classList
.
add
(
'
settings-group-first
'
);
}
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
ISettingNumberItemTemplate
):
void
{
super
.
renderSettingElement
(
element
,
index
,
templateData
);
}
pr
ivate
renderNewExtensionsElement
(
element
:
SettingsTreeNewExtensionsElement
,
template
:
ISettingNewExtensionsTemplate
):
void
{
template
.
context
=
element
;
}
pr
otected
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingNumberItemTemplate
,
onChange
:
(
value
:
number
)
=>
void
):
void
{
const
numParseFn
=
(
dataElement
.
valueType
===
'
integer
'
||
dataElement
.
valueType
===
'
nullable-integer
'
)
?
parseInt
:
parseFloat
;
public
getSettingDOMElementForDOMElement
(
domElement
:
HTMLElement
):
HTMLElement
{
const
parent
=
DOM
.
findParentWithClass
(
domElement
,
'
setting-item
'
);
if
(
parent
)
{
return
parent
;
}
const
nullNumParseFn
=
(
dataElement
.
valueType
===
'
nullable-integer
'
||
dataElement
.
valueType
===
'
nullable-number
'
)
?
(
v
=>
v
===
''
?
null
:
numParseFn
(
v
))
:
numParseFn
;
return
null
;
}
const
label
=
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_NUMBER_TEMPLATE_ID
,
template
);
public
getDOMElementsForSettingKey
(
treeContainer
:
HTMLElement
,
key
:
string
):
NodeListOf
<
HTMLElement
>
{
return
treeContainer
.
querySelectorAll
(
`[
${
SettingsRenderer
.
SETTING_KEY_ATTR
}
="
${
key
}
"]`
)
;
}
template
.
onChange
=
null
;
template
.
inputBox
.
value
=
dataElement
.
value
;
template
.
onChange
=
value
=>
{
renderValidations
(
dataElement
,
template
,
false
,
label
);
onChange
(
nullNumParseFn
(
value
));
};
public
getKeyForDOMElementInSetting
(
element
:
HTMLElement
):
string
{
const
settingElement
=
this
.
getSettingDOMElementForDOMElement
(
element
);
return
settingElement
&&
settingElement
.
getAttribute
(
SettingsRenderer
.
SETTING_KEY_ATTR
);
renderValidations
(
dataElement
,
template
,
true
,
label
);
}
}
private
renderSettingElement
(
tree
:
ITree
,
element
:
SettingsTreeSettingElement
,
templateId
:
string
,
template
:
ISettingItemTemplate
|
ISettingBoolItemTemplate
):
void
{
template
.
context
=
element
;
template
.
toolbar
.
context
=
element
;
const
setting
=
element
.
setting
;
DOM
.
toggleClass
(
template
.
containerElement
,
'
is-configured
'
,
element
.
isConfigured
);
DOM
.
toggleClass
(
template
.
containerElement
,
'
is-expanded
'
,
true
);
template
.
containerElement
.
setAttribute
(
SettingsRenderer
.
SETTING_KEY_ATTR
,
element
.
setting
.
key
);
const
titleTooltip
=
setting
.
key
+
(
element
.
isConfigured
?
'
- Modified
'
:
''
);
template
.
categoryElement
.
textContent
=
element
.
displayCategory
&&
(
element
.
displayCategory
+
'
:
'
);
template
.
categoryElement
.
title
=
titleTooltip
;
template
.
labelElement
.
textContent
=
element
.
displayLabel
;
template
.
labelElement
.
title
=
titleTooltip
;
export
class
SettingBoolRenderer
extends
AbstractSettingRenderer
implements
ITreeRenderer
<
SettingsTreeSettingElement
,
never
,
ISettingBoolItemTemplate
>
{
templateId
=
SETTINGS_BOOL_TEMPLATE_ID
;
template
.
descriptionElement
.
innerHTML
=
''
;
if
(
element
.
setting
.
descriptionIsMarkdown
)
{
const
renderedDescription
=
this
.
renderDescriptionMarkdown
(
element
,
element
.
description
,
template
.
toDispose
);
template
.
descriptionElement
.
appendChild
(
renderedDescription
);
}
else
{
template
.
descriptionElement
.
innerText
=
element
.
description
;
}
renderTemplate
(
container
:
HTMLElement
):
ISettingBoolItemTemplate
{
DOM
.
addClass
(
container
,
'
setting-item
'
);
DOM
.
addClass
(
container
,
'
setting-item-bool
'
);
const
baseId
=
(
element
.
displayCategory
+
'
_
'
+
element
.
displayLabel
).
replace
(
/ /g
,
'
_
'
).
toLowerCase
();
template
.
descriptionElement
.
id
=
baseId
+
'
_setting_description
'
;
const
titleElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-title
'
));
const
categoryElement
=
DOM
.
append
(
titleElement
,
$
(
'
span.setting-item-category
'
));
const
labelElement
=
DOM
.
append
(
titleElement
,
$
(
'
span.setting-item-label
'
));
const
otherOverridesElement
=
DOM
.
append
(
titleElement
,
$
(
'
span.setting-item-overrides
'
));
template
.
otherOverridesElement
.
innerHTML
=
''
;
const
descriptionAndValueElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-value-description
'
));
const
controlElement
=
DOM
.
append
(
descriptionAndValueElement
,
$
(
'
.setting-item-bool-control
'
));
const
descriptionElement
=
DOM
.
append
(
descriptionAndValueElement
,
$
(
'
.setting-item-description
'
));
const
modifiedIndicatorElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-modified-indicator
'
));
modifiedIndicatorElement
.
title
=
localize
(
'
modified
'
,
"
Modified
"
);
if
(
element
.
overriddenScopeList
.
length
)
{
const
otherOverridesLabel
=
element
.
isConfigured
?
localize
(
'
alsoConfiguredIn
'
,
"
Also modified in
"
)
:
localize
(
'
configuredIn
'
,
"
Modified in
"
);
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
span
'
,
null
,
`(
${
otherOverridesLabel
}
: `
));
const
deprecationWarningElement
=
DOM
.
append
(
container
,
$
(
'
.setting-item-deprecation-message
'
));
for
(
let
i
=
0
;
i
<
element
.
overriddenScopeList
.
length
;
i
++
)
{
let
view
=
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
a.modified-scope
'
,
null
,
element
.
overriddenScopeList
[
i
]));
const
toDispose
:
IDisposable
[]
=
[];
const
checkbox
=
new
Checkbox
({
actionClassName
:
'
setting-value-checkbox
'
,
isChecked
:
true
,
title
:
''
,
inputActiveOptionBorder
:
null
});
controlElement
.
appendChild
(
checkbox
.
domNode
);
toDispose
.
push
(
checkbox
);
toDispose
.
push
(
checkbox
.
onChange
(()
=>
{
if
(
template
.
onChange
)
{
template
.
onChange
(
checkbox
.
checked
);
}
}));
if
(
i
!==
element
.
overriddenScopeList
.
length
-
1
)
{
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
span
'
,
null
,
'
,
'
));
}
else
{
DOM
.
append
(
template
.
otherOverridesElement
,
$
(
'
span
'
,
null
,
'
)
'
))
;
}
// Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety
// Also have to ignore embedded links - too buried to stop propagation
toDispose
.
push
(
DOM
.
addDisposableListener
(
descriptionElement
,
DOM
.
EventType
.
MOUSE_DOWN
,
(
e
)
=>
{
const
targetElement
=
<
HTMLElement
>
e
.
target
;
const
targetId
=
descriptionElement
.
getAttribute
(
'
checkbox_label_target_id
'
);
DOM
.
addStandardDisposableListener
(
view
,
DOM
.
EventType
.
CLICK
,
(
e
:
IMouseEvent
)
=>
{
this
.
_onDidClickOverrideElement
.
fire
({
targetKey
:
element
.
setting
.
key
,
scope
:
element
.
overriddenScopeList
[
i
]
});
e
.
preventDefault
();
e
.
stopPropagation
();
});
// Make sure we are not a link and the target ID matches
// Toggle target checkbox
if
(
targetElement
.
tagName
.
toLowerCase
()
!==
'
a
'
&&
targetId
===
template
.
checkbox
.
domNode
.
id
)
{
template
.
checkbox
.
checked
=
template
.
checkbox
.
checked
?
false
:
true
;
template
.
onChange
(
checkbox
.
checked
);
}
DOM
.
EventHelper
.
stop
(
e
);
}));
}
this
.
renderValue
(
element
,
templateId
,
<
ISettingItemTemplate
>
template
);
// Remove tree attributes - sometimes overridden by tree - should be managed there
template
.
containerElement
.
parentElement
.
removeAttribute
(
'
role
'
);
template
.
containerElement
.
parentElement
.
removeAttribute
(
'
aria-level
'
);
template
.
containerElement
.
parentElement
.
removeAttribute
(
'
aria-posinset
'
);
template
.
containerElement
.
parentElement
.
removeAttribute
(
'
aria-setsize
'
);
}
checkbox
.
domNode
.
classList
.
add
(
AbstractSettingRenderer
.
CONTROL_CLASS
);
const
toolbarContainer
=
DOM
.
append
(
container
,
$
(
'
.setting-toolbar-container
'
));
const
toolbar
=
this
.
renderSettingToolbar
(
toolbarContainer
);
toDispose
.
push
(
toolbar
);
private
renderDescriptionMarkdown
(
element
:
SettingsTreeSettingElement
,
text
:
string
,
disposeables
:
IDisposable
[]):
HTMLElement
{
// Rewrite `#editor.fontSize#` to link format
text
=
fixSettingLinks
(
text
);
const
template
:
ISettingBoolItemTemplate
=
{
toDispose
,
const
renderedMarkdown
=
renderMarkdown
({
value
:
text
},
{
actionHandler
:
{
callback
:
(
content
:
string
)
=>
{
if
(
startsWith
(
content
,
'
#
'
))
{
const
e
:
ISettingLinkClickEvent
=
{
source
:
element
,
targetKey
:
content
.
substr
(
1
)
};
this
.
_onDidClickSettingLink
.
fire
(
e
);
}
else
{
let
uri
:
URI
;
try
{
uri
=
URI
.
parse
(
content
);
}
catch
(
err
)
{
// ignore
}
if
(
uri
)
{
this
.
openerService
.
open
(
uri
).
catch
(
onUnexpectedError
);
}
}
},
disposeables
containerElement
:
container
,
categoryElement
,
labelElement
,
controlElement
,
checkbox
,
descriptionElement
,
deprecationWarningElement
,
otherOverridesElement
,
toolbar
};
this
.
addSettingElementFocusHandler
(
template
);
// Prevent clicks from being handled by list
toDispose
.
push
(
DOM
.
addDisposableListener
(
controlElement
,
'
mousedown
'
,
(
e
:
IMouseEvent
)
=>
e
.
stopPropagation
()));
toDispose
.
push
(
DOM
.
addStandardDisposableListener
(
controlElement
,
'
keydown
'
,
(
e
:
StandardKeyboardEvent
)
=>
{
if
(
e
.
keyCode
===
KeyCode
.
Escape
)
{
e
.
browserEvent
.
stopPropagation
();
}
});
})
)
;
renderedMarkdown
.
classList
.
add
(
'
setting-item-description-markdown
'
);
cleanRenderedMarkdown
(
renderedMarkdown
);
return
renderedMarkdown
;
return
template
;
}
private
renderValue
(
element
:
SettingsTreeSettingElement
,
templateId
:
string
,
template
:
ISettingItemTemplate
|
ISettingBoolItemTemplate
):
void
{
const
onChange
=
value
=>
this
.
_onDidChangeSetting
.
fire
({
key
:
element
.
setting
.
key
,
value
,
type
:
template
.
context
.
valueType
});
template
.
deprecationWarningElement
.
innerText
=
element
.
setting
.
deprecationMessage
||
''
;
if
(
templateId
===
SETTINGS_ENUM_TEMPLATE_ID
)
{
this
.
renderEnum
(
element
,
<
ISettingEnumItemTemplate
>
template
,
onChange
);
}
else
if
(
templateId
===
SETTINGS_TEXT_TEMPLATE_ID
)
{
this
.
renderText
(
element
,
<
ISettingTextItemTemplate
>
template
,
onChange
);
}
else
if
(
templateId
===
SETTINGS_NUMBER_TEMPLATE_ID
)
{
this
.
renderNumber
(
element
,
<
ISettingTextItemTemplate
>
template
,
onChange
);
}
else
if
(
templateId
===
SETTINGS_BOOL_TEMPLATE_ID
)
{
this
.
renderBool
(
element
,
<
ISettingBoolItemTemplate
>
template
,
onChange
);
}
else
if
(
templateId
===
SETTINGS_EXCLUDE_TEMPLATE_ID
)
{
this
.
renderExcludeSetting
(
element
,
<
ISettingExcludeItemTemplate
>
template
);
}
else
if
(
templateId
===
SETTINGS_COMPLEX_TEMPLATE_ID
)
{
this
.
renderComplexSetting
(
element
,
<
ISettingComplexItemTemplate
>
template
);
}
renderElement
(
element
:
ITreeNode
<
SettingsTreeSettingElement
,
never
>
,
index
:
number
,
templateData
:
ISettingBoolItemTemplate
):
void
{
super
.
renderSettingElement
(
element
,
index
,
templateData
);
}
pr
ivate
renderBool
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingBoolItemTemplate
,
onChange
:
(
value
:
boolean
)
=>
void
):
void
{
pr
otected
renderValue
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingBoolItemTemplate
,
onChange
:
(
value
:
boolean
)
=>
void
):
void
{
template
.
onChange
=
null
;
template
.
checkbox
.
checked
=
dataElement
.
value
;
template
.
onChange
=
onChange
;
...
...
@@ -1161,132 +997,99 @@ export class SettingsRenderer implements ITreeRenderer {
// Setup and add ARIA attributes
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_BOOL_TEMPLATE_ID
,
template
);
}
}
private
renderEnum
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingEnumItemTemplate
,
onChange
:
(
value
:
string
)
=>
void
):
void
{
const
enumDescriptions
=
dataElement
.
setting
.
enumDescriptions
;
const
enumDescriptionsAreMarkdown
=
dataElement
.
setting
.
enumDescriptionsAreMarkdown
;
let
displayOptions
=
dataElement
.
setting
.
enum
.
map
(
String
)
.
map
(
escapeInvisibleChars
)
.
map
((
data
,
index
)
=>
<
ISelectOptionItem
>
{
text
:
data
,
description
:
(
enumDescriptions
&&
enumDescriptions
[
index
]
&&
(
enumDescriptionsAreMarkdown
?
fixSettingLinks
(
enumDescriptions
[
index
],
false
)
:
enumDescriptions
[
index
])),
descriptionIsMarkdown
:
enumDescriptionsAreMarkdown
,
decoratorRight
:
(
data
===
dataElement
.
defaultValue
?
localize
(
'
settings.Default
'
,
"
{0}
"
,
'
default
'
)
:
''
)
});
template
.
selectBox
.
setOptions
(
displayOptions
);
const
label
=
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_ENUM_TEMPLATE_ID
,
template
);
template
.
selectBox
.
setAriaLabel
(
label
);
const
idx
=
dataElement
.
setting
.
enum
.
indexOf
(
dataElement
.
value
);
template
.
onChange
=
null
;
template
.
selectBox
.
select
(
idx
);
template
.
onChange
=
idx
=>
onChange
(
dataElement
.
setting
.
enum
[
idx
]);
template
.
enumDescriptionElement
.
innerHTML
=
''
;
}
export
class
SettingTreeRenderers
{
public
readonly
onDidClickOverrideElement
:
Event
<
ISettingOverrideClickEvent
>
;
private
renderText
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingTextItemTemplate
,
onChange
:
(
value
:
string
)
=>
void
):
void
{
private
readonly
_onDidChangeSetting
=
new
Emitter
<
ISettingChangeEvent
>
();
public
readonly
onDidChangeSetting
:
Event
<
ISettingChangeEvent
>
;
const
label
=
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_TEXT_TEMPLATE_ID
,
template
)
;
public
readonly
onDidOpenSettings
:
Event
<
string
>
;
template
.
onChange
=
null
;
template
.
inputBox
.
value
=
dataElement
.
value
;
template
.
onChange
=
value
=>
{
renderValidations
(
dataElement
,
template
,
false
,
label
);
onChange
(
value
);
};
public
readonly
onDidClickSettingLink
:
Event
<
ISettingLinkClickEvent
>
;
renderValidations
(
dataElement
,
template
,
true
,
label
);
}
public
readonly
onDidFocusSetting
:
Event
<
SettingsTreeSettingElement
>
;
private
renderNumber
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingTextItemTemplate
,
onChange
:
(
value
:
number
)
=>
void
):
void
{
const
numParseFn
=
(
dataElement
.
valueType
===
'
integer
'
||
dataElement
.
valueType
===
'
nullable-integer
'
)
?
parseInt
:
parseFloat
;
public
readonly
allRenderers
:
ITreeRenderer
<
SettingsTreeElement
,
never
,
any
>
[];
const
nullNumParseFn
=
(
dataElement
.
valueType
===
'
nullable-integer
'
||
dataElement
.
valueType
===
'
nullable-number
'
)
?
(
v
=>
v
===
''
?
null
:
numParseFn
(
v
))
:
numParseFn
;
private
readonly
settingActions
:
IAction
[];
const
label
=
this
.
setElementAriaLabels
(
dataElement
,
SETTINGS_NUMBER_TEMPLATE_ID
,
template
);
constructor
(
@
IInstantiationService
private
readonly
_instantiationService
:
IInstantiationService
,
@
IContextMenuService
private
readonly
_contextMenuService
:
IContextMenuService
,
@
IContextViewService
private
readonly
_contextViewService
:
IContextViewService
)
{
this
.
settingActions
=
[
new
Action
(
'
settings.resetSetting
'
,
localize
(
'
resetSettingLabel
'
,
"
Reset Setting
"
),
undefined
,
undefined
,
(
context
:
SettingsTreeSettingElement
)
=>
{
if
(
context
)
{
this
.
_onDidChangeSetting
.
fire
({
key
:
context
.
setting
.
key
,
value
:
undefined
,
type
:
context
.
setting
.
type
as
SettingValueType
});
}
template
.
onChange
=
null
;
template
.
inputBox
.
value
=
dataElement
.
value
;
template
.
onChange
=
value
=>
{
renderValidations
(
dataElement
,
template
,
false
,
label
);
onChange
(
nullNumParseFn
(
value
));
};
return
Promise
.
resolve
(
null
);
}),
new
Separator
(),
this
.
_instantiationService
.
createInstance
(
CopySettingIdAction
),
this
.
_instantiationService
.
createInstance
(
CopySettingAsJSONAction
),
];
renderValidations
(
dataElement
,
template
,
true
,
label
);
}
const
settingRenderers
=
[
this
.
_instantiationService
.
createInstance
(
SettingBoolRenderer
,
this
.
settingActions
),
this
.
_instantiationService
.
createInstance
(
SettingNumberRenderer
,
this
.
settingActions
),
this
.
_instantiationService
.
createInstance
(
SettingBoolRenderer
,
this
.
settingActions
),
this
.
_instantiationService
.
createInstance
(
SettingComplexRenderer
,
this
.
settingActions
),
this
.
_instantiationService
.
createInstance
(
SettingTextRenderer
,
this
.
settingActions
),
this
.
_instantiationService
.
createInstance
(
SettingExcludeRenderer
,
this
.
settingActions
),
this
.
_instantiationService
.
createInstance
(
SettingEnumRenderer
,
this
.
settingActions
),
];
private
renderExcludeSetting
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingExcludeItemTemplate
):
void
{
const
value
=
getExcludeDisplayValue
(
dataElement
);
template
.
excludeWidget
.
setValue
(
value
);
template
.
context
=
dataElement
;
this
.
onDidClickOverrideElement
=
Event
.
any
(...
settingRenderers
.
map
(
r
=>
r
.
onDidClickOverrideElement
));
this
.
onDidChangeSetting
=
Event
.
any
(
...
settingRenderers
.
map
(
r
=>
r
.
onDidChangeSetting
),
this
.
_onDidChangeSetting
.
event
);
this
.
onDidOpenSettings
=
Event
.
any
(...
settingRenderers
.
map
(
r
=>
r
.
onDidOpenSettings
));
this
.
onDidClickSettingLink
=
Event
.
any
(...
settingRenderers
.
map
(
r
=>
r
.
onDidClickSettingLink
));
this
.
onDidFocusSetting
=
Event
.
any
(...
settingRenderers
.
map
(
r
=>
r
.
onDidFocusSetting
));
this
.
allRenderers
=
[
...
settingRenderers
,
this
.
_instantiationService
.
createInstance
(
SettingGroupRenderer
),
this
.
_instantiationService
.
createInstance
(
SettingNewExtensionsRenderer
),
];
}
p
rivate
renderComplexSetting
(
dataElement
:
SettingsTreeSettingElement
,
template
:
ISettingComplexItemTemplate
):
void
{
t
emplate
.
onChange
=
()
=>
this
.
_onDidOpenSettings
.
fire
(
dataElement
.
setting
.
key
);
p
ublic
cancelSuggesters
()
{
t
his
.
_contextViewService
.
hideContextView
(
);
}
private
setElementAriaLabels
(
dataElement
:
SettingsTreeSettingElement
,
templateId
:
string
,
template
:
ISettingItemTemplate
):
string
{
// Create base Id for element references
const
baseId
=
(
dataElement
.
displayCategory
+
'
_
'
+
dataElement
.
displayLabel
).
replace
(
/ /g
,
'
_
'
).
toLowerCase
();
const
modifiedText
=
template
.
otherOverridesElement
.
textContent
?
template
.
otherOverridesElement
.
textContent
:
(
dataElement
.
isConfigured
?
localize
(
'
settings.Modified
'
,
'
Modified.
'
)
:
''
);
let
itemElement
=
null
;
// Use '.' as reader pause
let
label
=
dataElement
.
displayCategory
+
'
'
+
dataElement
.
displayLabel
+
'
.
'
;
// Setup and add ARIA attributes
// Create id and label for control/input element - parent is wrapper div
if
(
templateId
===
SETTINGS_TEXT_TEMPLATE_ID
)
{
if
(
itemElement
=
(
<
ISettingTextItemTemplate
>
template
).
inputBox
.
inputElement
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
textbox
'
);
label
+=
modifiedText
;
}
}
else
if
(
templateId
===
SETTINGS_NUMBER_TEMPLATE_ID
)
{
if
(
itemElement
=
(
<
ISettingNumberItemTemplate
>
template
).
inputBox
.
inputElement
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
textbox
'
);
label
+=
'
number.
'
+
modifiedText
;
}
}
else
if
(
templateId
===
SETTINGS_BOOL_TEMPLATE_ID
)
{
if
(
itemElement
=
(
<
ISettingBoolItemTemplate
>
template
).
checkbox
.
domNode
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
checkbox
'
);
label
+=
modifiedText
;
// Add checkbox target to description clickable and able to toggle checkbox
template
.
descriptionElement
.
setAttribute
(
'
checkbox_label_target_id
'
,
baseId
+
'
_setting_item
'
);
}
}
else
if
(
templateId
===
SETTINGS_ENUM_TEMPLATE_ID
)
{
if
(
itemElement
=
template
.
controlElement
.
firstElementChild
)
{
itemElement
.
setAttribute
(
'
role
'
,
'
combobox
'
);
label
+=
modifiedText
;
}
}
else
{
// Don't change attributes if we don't know what we areFunctions
return
''
;
showContextMenu
(
element
:
SettingsTreeSettingElement
,
settingDOMElement
:
HTMLElement
):
void
{
const
toolbarElement
:
HTMLElement
=
settingDOMElement
.
querySelector
(
'
.toolbar-toggle-more
'
);
if
(
toolbarElement
)
{
this
.
_contextMenuService
.
showContextMenu
({
getActions
:
()
=>
this
.
settingActions
,
getAnchor
:
()
=>
toolbarElement
,
getActionsContext
:
()
=>
element
});
}
}
// We don't have control element, return empty label
if
(
!
itemElement
)
{
return
''
;
getSettingDOMElementForDOMElement
(
domElement
:
HTMLElement
):
HTMLElement
{
const
parent
=
DOM
.
findParentWithClass
(
domElement
,
'
setting-item
'
);
if
(
parent
)
{
return
parent
;
}
// Labels will not be read on descendent input elements of the parent treeitem
// unless defined as roles for input items
// voiceover does not seem to use labeledby correctly, set labels directly on input elements
itemElement
.
id
=
baseId
+
'
_setting_item
'
;
itemElement
.
setAttribute
(
'
aria-label
'
,
label
);
itemElement
.
setAttribute
(
'
aria-describedby
'
,
baseId
+
'
_setting_description settings_aria_more_actions_shortcut_label
'
);
return
null
;
}
return
label
;
getDOMElementsForSettingKey
(
treeContainer
:
HTMLElement
,
key
:
string
):
NodeListOf
<
HTMLElement
>
{
return
treeContainer
.
querySelectorAll
(
`[
${
AbstractSettingRenderer
.
SETTING_KEY_ATTR
}
="
${
key
}
"]`
);
}
disposeTemplate
(
tree
:
ITree
,
templateId
:
string
,
template
:
IDisposableTemplate
):
void
{
dispose
(
template
.
toDispose
);
getKeyForDOMElementInSetting
(
element
:
HTMLElement
):
string
{
const
settingElement
=
this
.
getSettingDOMElementForDOMElement
(
element
);
return
settingElement
&&
settingElement
.
getAttribute
(
AbstractSettingRenderer
.
SETTING_KEY_ATTR
);
}
}
...
...
@@ -1395,22 +1198,62 @@ export class SettingsTreeFilter implements IFilter {
}
}
export
class
SettingsTree
Controller
extends
WorkbenchTreeController
{
export
class
SettingsTree
Filter2
implements
ITreeFilter
<
SettingsTreeElement
>
{
constructor
(
@
IConfigurationService
configurationService
:
IConfigurationService
)
{
super
({},
configurationService
);
}
private
viewState
:
ISettingsEditorViewState
,
)
{
}
filter
(
element
:
SettingsTreeElement
,
parentVisibility
:
TreeVisibility
):
TreeFilterResult
<
void
>
{
// Filter during search
if
(
this
.
viewState
.
filterToCategory
&&
element
instanceof
SettingsTreeSettingElement
)
{
if
(
!
this
.
settingContainedInGroup
(
element
.
setting
,
this
.
viewState
.
filterToCategory
))
{
return
false
;
}
}
// Non-user scope selected
if
(
element
instanceof
SettingsTreeSettingElement
&&
this
.
viewState
.
settingsTarget
!==
ConfigurationTarget
.
USER
)
{
if
(
!
element
.
matchesScope
(
this
.
viewState
.
settingsTarget
))
{
return
false
;
}
}
// @modified or tag
if
(
element
instanceof
SettingsTreeSettingElement
&&
this
.
viewState
.
tagFilters
)
{
if
(
!
element
.
matchesAllTags
(
this
.
viewState
.
tagFilters
))
{
return
false
;
}
}
// Group with no visible children
if
(
element
instanceof
SettingsTreeGroupElement
)
{
if
(
typeof
element
.
count
===
'
number
'
)
{
return
element
.
count
>
0
;
}
protected
onLeftClick
(
tree
:
ITree
,
element
:
any
,
eventish
:
IMouseEvent
,
origin
?:
string
):
boolean
{
const
isLink
=
eventish
.
target
.
tagName
.
toLowerCase
()
===
'
a
'
||
eventish
.
target
.
parentElement
.
tagName
.
toLowerCase
()
===
'
a
'
;
// <code> inside <a>
return
TreeVisibility
.
Recurse
;
}
if
(
isLink
&&
(
DOM
.
findParentWithClass
(
eventish
.
target
,
'
setting-item-description-markdown
'
,
tree
.
getHTMLElement
())
||
DOM
.
findParentWithClass
(
eventish
.
target
,
'
select-box-description-markdown
'
)))
{
return
true
;
// Filtered "new extensions" button
if
(
element
instanceof
SettingsTreeNewExtensionsElement
)
{
if
((
this
.
viewState
.
tagFilters
&&
this
.
viewState
.
tagFilters
.
size
)
||
this
.
viewState
.
filterToCategory
)
{
return
false
;
}
}
return
false
;
return
true
;
}
private
settingContainedInGroup
(
setting
:
ISetting
,
group
:
SettingsTreeGroupElement
):
boolean
{
return
group
.
children
.
some
(
child
=>
{
if
(
child
instanceof
SettingsTreeGroupElement
)
{
return
this
.
settingContainedInGroup
(
setting
,
child
);
}
else
if
(
child
instanceof
SettingsTreeSettingElement
)
{
return
child
.
setting
.
key
===
setting
.
key
;
}
else
{
return
false
;
}
});
}
}
...
...
@@ -1435,112 +1278,96 @@ export class SettingsAccessibilityProvider implements IAccessibilityProvider {
}
}
class
NonExpandableOrSelectableTree
extends
Tree
{
expand
():
Promise
<
any
>
{
return
Promise
.
resolve
(
null
);
}
collapse
():
Promise
<
any
>
{
return
Promise
.
resolve
(
null
);
}
public
setFocus
(
element
?:
any
,
eventPayload
?:
any
):
void
{
return
;
}
class
SettingsTreeDelegate
implements
IListVirtualDelegate
<
SettingsTreeGroupChild
>
{
getHeight
(
element
:
SettingsTreeElement
):
number
{
if
(
element
instanceof
SettingsTreeGroupElement
)
{
if
(
element
.
isFirstGroup
)
{
return
31
;
}
public
focusNext
(
count
?:
number
,
eventPayload
?:
any
):
void
{
return
;
}
return
40
+
(
7
*
element
.
level
);
}
public
focusPrevious
(
count
?:
number
,
eventPayload
?:
any
):
void
{
return
;
return
78
;
}
public
focusParent
(
eventPayload
?:
any
):
void
{
return
;
}
getTemplateId
(
element
:
SettingsTreeGroupElement
|
SettingsTreeSettingElement
|
SettingsTreeNewExtensionsElement
):
string
{
if
(
element
instanceof
SettingsTreeGroupElement
)
{
return
SETTINGS_ELEMENT_TEMPLATE_ID
;
}
public
focusFirstChild
(
eventPayload
?:
any
):
void
{
return
;
}
if
(
element
instanceof
SettingsTreeSettingElement
)
{
if
(
element
.
valueType
===
'
boolean
'
)
{
return
SETTINGS_BOOL_TEMPLATE_ID
;
}
public
focusFirst
(
eventPayload
?:
any
,
from
?:
any
):
void
{
return
;
}
if
(
element
.
valueType
===
'
integer
'
||
element
.
valueType
===
'
number
'
||
element
.
valueType
===
'
nullable-integer
'
||
element
.
valueType
===
'
nullable-number
'
)
{
return
SETTINGS_NUMBER_TEMPLATE_ID
;
}
public
focusNth
(
index
:
number
,
eventPayload
?:
any
):
void
{
return
;
}
if
(
element
.
valueType
===
'
string
'
)
{
return
SETTINGS_TEXT_TEMPLATE_ID
;
}
public
focusLast
(
eventPayload
?:
any
,
from
?:
any
):
void
{
return
;
}
if
(
element
.
valueType
===
'
enum
'
)
{
return
SETTINGS_ENUM_TEMPLATE_ID
;
}
public
focusNextPage
(
eventPayload
?:
any
):
void
{
return
;
}
if
(
element
.
valueType
===
'
exclude
'
)
{
return
SETTINGS_EXCLUDE_TEMPLATE_ID
;
}
public
focusPreviousPage
(
eventPayload
?:
any
):
void
{
return
;
}
return
SETTINGS_COMPLEX_TEMPLATE_ID
;
}
public
select
(
element
:
any
,
eventPayload
?:
any
):
void
{
return
;
}
if
(
element
instanceof
SettingsTreeNewExtensionsElement
)
{
return
SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID
;
}
public
selectRange
(
fromElement
:
any
,
toElement
:
any
,
eventPayload
?:
any
):
void
{
return
;
throw
new
Error
(
'
unknown element type:
'
+
element
);
}
public
selectAll
(
elements
:
any
[],
eventPayload
?:
any
):
void
{
return
;
hasDynamicHeight
(
element
:
SettingsTreeGroupElement
|
SettingsTreeSettingElement
|
SettingsTreeNewExtensionsElement
):
boolean
{
return
!
(
element
instanceof
SettingsTreeGroupElement
)
;
}
}
public
setSelection
(
elements
:
any
[],
eventPayload
?:
any
):
void
{
return
;
class
NonCollapsibleObjectTreeModel
<
T
>
extends
ObjectTreeModel
<
T
>
{
isCollapsible
(
element
:
T
):
boolean
{
return
false
;
}
public
toggleSelection
(
element
:
any
,
eventPayload
?:
any
):
void
{
return
;
setCollapsed
(
element
:
T
,
collapsed
?:
boolean
,
recursive
?:
boolean
):
boolean
{
return
false
;
}
}
export
class
SettingsTree
extends
NonExpandableOrSelectableTree
{
export
class
SettingsTree
extends
ObjectTree
<
SettingsTreeElement
>
{
protected
disposables
:
IDisposable
[];
constructor
(
container
:
HTMLElement
,
viewState
:
ISettingsEditorViewState
,
configuration
:
Partial
<
ITreeConfiguration
>
,
@
IThemeService
themeService
:
IThemeService
,
@
IInstantiationService
instantiationService
:
IInstantiationService
renderers
:
ITreeRenderer
<
any
,
void
,
any
>
[],
@
IThemeService
themeService
:
IThemeService
)
{
const
treeClass
=
'
settings-editor-tree
'
;
const
controller
=
instantiationService
.
createInstance
(
SettingsTreeController
);
const
fullConfiguration
=
<
ITreeConfiguration
>
{
controller
,
accessibilityProvider
:
instantiationService
.
createInstance
(
SettingsAccessibilityProvider
),
filter
:
instantiationService
.
createInstance
(
SettingsTreeFilter
,
viewState
),
styler
:
new
DefaultTreestyler
(
DOM
.
createStyleSheet
(
container
),
treeClass
),
...
configuration
};
const
options
=
{
ariaLabel
:
localize
(
'
treeAriaLabel
'
,
"
Settings
"
),
showLoading
:
false
,
indentPixels
:
0
,
twistiePixels
:
20
,
// Actually for gear button
};
super
(
container
,
fullConfiguration
,
options
);
new
SettingsTreeDelegate
(),
renderers
,
{
supportDynamicHeights
:
true
,
ariaLabel
:
localize
(
'
treeAriaLabel
'
,
"
Settings
"
),
identityProvider
:
{
getId
(
e
)
{
return
e
.
id
;
}
},
filter
:
new
SettingsTreeFilter2
(
viewState
)
});
this
.
disposables
=
[];
this
.
disposables
.
push
(
controller
);
this
.
disposables
.
push
(
registerThemingParticipant
((
theme
:
ITheme
,
collector
:
ICssStyleCollector
)
=>
{
const
activeBorderColor
=
theme
.
getColor
(
focusBorder
);
if
(
activeBorderColor
)
{
...
...
@@ -1609,6 +1436,10 @@ export class SettingsTree extends NonExpandableOrSelectableTree {
}));
}
protected
createModel
(
view
:
ISpliceable
<
ITreeNode
<
SettingsTreeGroupChild
>>
,
options
:
IObjectTreeOptions
<
SettingsTreeGroupChild
>
):
ITreeModel
<
SettingsTreeGroupChild
|
null
,
void
,
SettingsTreeGroupChild
|
null
>
{
return
new
NonCollapsibleObjectTreeModel
<
SettingsTreeGroupChild
>
(
view
,
options
);
}
public
dispose
():
void
{
this
.
disposables
=
dispose
(
this
.
disposables
);
}
...
...
src/vs/workbench/parts/preferences/browser/settingsWidgets.ts
浏览文件 @
da732ab0
...
...
@@ -191,7 +191,7 @@ export class ExcludeSettingListModel {
}
}
interface
IExcludeChangeEvent
{
export
interface
IExcludeChangeEvent
{
originalPattern
:
string
;
pattern
?:
string
;
sibling
?:
string
;
...
...
src/vs/workbench/parts/preferences/electron-browser/media/settingsEditor2.css
浏览文件 @
da732ab0
...
...
@@ -113,30 +113,35 @@
text-decoration
:
underline
;
}
.settings-editor.no-toc-search
>
.settings-body
.settings-tree-container
.monaco-
tree-wrapper
,
.settings-editor.narrow-width
>
.settings-body
.settings-tree-container
.monaco-
tree-wrapper
{
.settings-editor.no-toc-search
>
.settings-body
.settings-tree-container
.monaco-
list-rows
,
.settings-editor.narrow-width
>
.settings-body
.settings-tree-container
.monaco-
list-rows
{
margin-left
:
0px
;
}
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree-wrapper
{
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list-rows
{
max-width
:
1000px
;
margin
:
auto
;
}
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-tree-wrapper
.monaco-tree-rows
{
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-list-row
{
line-height
:
1.4em
!important
;
/* TODO */
padding-left
:
208px
;
padding-right
:
24px
;
box-sizing
:
border-box
;
/* box-sizing: border-box; */
}
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-list-row
.monaco-tl-row
{
position
:
relative
;
}
.settings-editor.no-toc-search
>
.settings-body
.settings-tree-container
.monaco-
tree-wrapper
.monaco-tree-rows
,
.settings-editor.narrow-width
>
.settings-body
.settings-tree-container
.monaco-
tree-wrapper
.monaco-tree-rows
{
.settings-editor.no-toc-search
>
.settings-body
.settings-tree-container
.monaco-
list-row
,
.settings-editor.narrow-width
>
.settings-body
.settings-tree-container
.monaco-
list-row
{
/* 3 margin + 20 padding + 2 border */
width
:
calc
(
100%
-
25px
);
padding-left
:
25px
;
}
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree-row
>
.content
::befor
e
{
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list-row
.monaco-tl-twisti
e
{
/* Hide twisties */
display
:
none
!important
;
}
...
...
@@ -162,10 +167,10 @@
width
:
26px
;
}
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree
-row
.mouseover
.setting-toolbar-container
>
.monaco-toolbar
.toolbar-toggle-more
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree
-row
.setting-item.focused
.setting-toolbar-container
>
.monaco-toolbar
.toolbar-toggle-more
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree
-row
.setting-toolbar-container
:hover
>
.monaco-toolbar
.toolbar-toggle-more
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree
-row
.setting-toolbar-container
>
.monaco-toolbar
.active
.toolbar-toggle-more
{
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list
-row
.mouseover
.setting-toolbar-container
>
.monaco-toolbar
.toolbar-toggle-more
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list
-row
.setting-item.focused
.setting-toolbar-container
>
.monaco-toolbar
.toolbar-toggle-more
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list
-row
.setting-toolbar-container
:hover
>
.monaco-toolbar
.toolbar-toggle-more
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list
-row
.setting-toolbar-container
>
.monaco-toolbar
.active
.toolbar-toggle-more
{
opacity
:
1
;
}
...
...
@@ -261,7 +266,7 @@
padding-left
:
31px
;
}
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
tree-wrapper
,
.settings-editor
>
.settings-body
.settings-tree-container
.monaco-
list-rows
,
.settings-editor
>
.settings-body
.settings-toc-wrapper
{
height
:
100%
;
max-width
:
1000px
;
...
...
@@ -273,7 +278,7 @@
margin-left
:
0px
;
}
.settings-editor
>
.settings-body
>
.settings-tree-container
.monaco-
tree
-row
{
.settings-editor
>
.settings-body
>
.settings-tree-container
.monaco-
list
-row
{
overflow
:
visible
;
/* so validation messages dont get clipped */
cursor
:
default
;
...
...
@@ -408,7 +413,7 @@
visibility
:
hidden
;
}
.settings-editor
>
.settings-body
.settings-tree-container
.setting-measure-container
.monaco-
tree
-row
{
.settings-editor
>
.settings-body
.settings-tree-container
.setting-measure-container
.monaco-
list
-row
{
padding-left
:
20px
;
}
...
...
@@ -426,6 +431,10 @@
display
:
block
;
}
.settings-editor
>
.settings-body
>
.settings-tree-container
.setting-item.setting-item-bool
{
padding-bottom
:
26px
;
}
.settings-editor
>
.settings-body
>
.settings-tree-container
.setting-item-bool
.setting-item-value-description
{
display
:
flex
;
cursor
:
pointer
;
...
...
src/vs/workbench/parts/preferences/electron-browser/settingsEditor2.ts
浏览文件 @
da732ab0
...
...
@@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import
*
as
DOM
from
'
vs/base/browser/dom
'
;
import
{
ITreeElement
}
from
'
vs/base/browser/ui/tree/tree
'
;
import
*
as
arrays
from
'
vs/base/common/arrays
'
;
import
{
Delayer
,
ThrottledDelayer
}
from
'
vs/base/common/async
'
;
import
{
CancellationToken
,
CancellationTokenSource
}
from
'
vs/base/common/cancellation
'
;
import
*
as
collections
from
'
vs/base/common/collections
'
;
import
{
getErrorMessage
,
isPromiseCanceledError
}
from
'
vs/base/common/errors
'
;
import
{
Iterator
}
from
'
vs/base/common/iterator
'
;
import
{
isArray
}
from
'
vs/base/common/types
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Tree
}
from
'
vs/base/parts/tree/browser/treeImpl
'
;
import
{
collapseAll
,
expandAll
}
from
'
vs/base/parts/tree/browser/treeUtils
'
;
import
'
vs/css!./media/settingsEditor2
'
;
import
{
localize
}
from
'
vs/nls
'
;
...
...
@@ -32,8 +33,8 @@ import { IEditor, IEditorMemento } from 'vs/workbench/common/editor';
import
{
attachSuggestEnabledInputBoxStyler
,
SuggestEnabledInput
}
from
'
vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput
'
;
import
{
SettingsTarget
,
SettingsTargetsWidget
}
from
'
vs/workbench/parts/preferences/browser/preferencesWidgets
'
;
import
{
commonlyUsedData
,
tocData
}
from
'
vs/workbench/parts/preferences/browser/settingsLayout
'
;
import
{
ISettingLinkClickEvent
,
ISettingOverrideClickEvent
,
resolveExtensionsSettings
,
resolveSettingsTree
,
SettingsDataSource
,
SettingsRenderer
,
SettingsTree
,
SimplePagedDataSource
}
from
'
vs/workbench/parts/preferences/browser/settingsTree
'
;
import
{
ISettingsEditorViewState
,
parseQuery
,
SearchResultIdx
,
SearchResultModel
,
SettingsTreeGroup
Element
,
SettingsTreeModel
,
SettingsTreeSettingElement
}
from
'
vs/workbench/parts/preferences/browser/settingsTreeModels
'
;
import
{
AbstractSettingRenderer
,
ISettingLinkClickEvent
,
ISettingOverrideClickEvent
,
resolveExtensionsSettings
,
resolveSettingsTree
,
SettingsTree
,
SettingTreeRenderers
}
from
'
vs/workbench/parts/preferences/browser/settingsTree
'
;
import
{
ISettingsEditorViewState
,
parseQuery
,
SearchResultIdx
,
SearchResultModel
,
SettingsTreeGroup
Child
,
SettingsTreeGroupElement
,
SettingsTreeModel
}
from
'
vs/workbench/parts/preferences/browser/settingsTreeModels
'
;
import
{
settingsTextInputBorder
}
from
'
vs/workbench/parts/preferences/browser/settingsWidgets
'
;
import
{
TOCRenderer
,
TOCTree
,
TOCTreeModel
}
from
'
vs/workbench/parts/preferences/browser/tocTree
'
;
import
{
CONTEXT_SETTINGS_EDITOR
,
CONTEXT_SETTINGS_SEARCH_FOCUS
,
CONTEXT_TOC_ROW_FOCUS
,
IPreferencesSearchService
,
ISearchProvider
,
MODIFIED_SETTING_TAG
,
SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU
}
from
'
vs/workbench/parts/preferences/common/preferences
'
;
...
...
@@ -42,6 +43,19 @@ import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEdit
import
{
SettingsEditor2Input
}
from
'
vs/workbench/services/preferences/common/preferencesEditorInput
'
;
import
{
Settings2EditorModel
}
from
'
vs/workbench/services/preferences/common/preferencesModels
'
;
function
createGroupIterator
(
group
:
SettingsTreeGroupElement
):
Iterator
<
ITreeElement
<
SettingsTreeGroupChild
>>
{
const
groupsIt
=
Iterator
.
fromArray
(
group
.
children
);
return
Iterator
.
map
(
groupsIt
,
g
=>
{
return
{
element
:
g
,
children
:
g
instanceof
SettingsTreeGroupElement
?
createGroupIterator
(
g
)
:
undefined
};
});
}
const
$
=
DOM
.
$
;
const
SETTINGS_EDITOR_STATE_KEY
=
'
settingsEditorState
'
;
...
...
@@ -76,9 +90,8 @@ export class SettingsEditor2 extends BaseEditor {
private
settingsTargetsWidget
:
SettingsTargetsWidget
;
private
settingsTreeContainer
:
HTMLElement
;
private
settingsTree
:
Tree
;
private
settingsTreeRenderer
:
SettingsRenderer
;
private
settingsTreeDataSource
:
SimplePagedDataSource
;
private
settingsTree
:
SettingsTree
;
private
settingRenderers
:
SettingTreeRenderers
;
private
tocTreeModel
:
TOCTreeModel
;
private
settingsTreeModel
:
SettingsTreeModel
;
private
noResultsMessage
:
HTMLElement
;
...
...
@@ -259,8 +272,8 @@ export class SettingsEditor2 extends BaseEditor {
}
layout
(
dimension
:
DOM
.
Dimension
):
void
{
const
firstEl
=
this
.
settingsTree
.
getFirstVisibleElement
();
const
firstElTop
=
this
.
settingsTree
.
getRelativeTop
(
firstEl
);
//
const firstEl = this.settingsTree.getFirstVisibleElement();
//
const firstElTop = this.settingsTree.getRelativeTop(firstEl);
this
.
layoutTrees
(
dimension
);
...
...
@@ -276,7 +289,7 @@ export class SettingsEditor2 extends BaseEditor {
this
.
lastLayedoutWidth
=
dimension
.
width
;
this
.
delayRefreshOnLayout
.
trigger
(()
=>
{
this
.
renderTree
(
undefined
,
true
).
then
(()
=>
{
this
.
settingsTree
.
reveal
(
firstEl
,
firstElTop
);
//
this.settingsTree.reveal(firstEl, firstElTop);
});
});
}
...
...
@@ -284,9 +297,9 @@ export class SettingsEditor2 extends BaseEditor {
focus
():
void
{
if
(
this
.
lastFocusedSettingElement
)
{
const
elements
=
this
.
setting
sTreeRenderer
.
getDOMElementsForSettingKey
(
this
.
settingsTree
.
getHTMLElement
(),
this
.
lastFocusedSettingElement
);
const
elements
=
this
.
setting
Renderers
.
getDOMElementsForSettingKey
(
this
.
settingsTree
.
getHTMLElement
(),
this
.
lastFocusedSettingElement
);
if
(
elements
.
length
)
{
const
control
=
elements
[
0
].
querySelector
(
Settings
Renderer
.
CONTROL_SELECTOR
);
const
control
=
elements
[
0
].
querySelector
(
AbstractSetting
Renderer
.
CONTROL_SELECTOR
);
if
(
control
)
{
(
<
HTMLElement
>
control
).
focus
();
return
;
...
...
@@ -307,26 +320,26 @@ export class SettingsEditor2 extends BaseEditor {
}
}
const
firstFocusable
=
this
.
settingsTree
.
getHTMLElement
().
querySelector
(
Settings
Renderer
.
CONTROL_SELECTOR
);
const
firstFocusable
=
this
.
settingsTree
.
getHTMLElement
().
querySelector
(
AbstractSetting
Renderer
.
CONTROL_SELECTOR
);
if
(
firstFocusable
)
{
(
<
HTMLElement
>
firstFocusable
).
focus
();
}
}
showContextMenu
():
void
{
const
settingDOMElement
=
this
.
setting
sTreeRenderer
.
getSettingDOMElementForDOMElement
(
this
.
getActiveElementInSettingsTree
());
const
settingDOMElement
=
this
.
setting
Renderers
.
getSettingDOMElementForDOMElement
(
this
.
getActiveElementInSettingsTree
());
if
(
!
settingDOMElement
)
{
return
;
}
const
focusedKey
=
this
.
setting
sTreeRenderer
.
getKeyForDOMElementInSetting
(
settingDOMElement
);
const
focusedKey
=
this
.
setting
Renderers
.
getKeyForDOMElementInSetting
(
settingDOMElement
);
if
(
!
focusedKey
)
{
return
;
}
const
elements
=
this
.
currentSettingsModel
.
getElementsByName
(
focusedKey
);
if
(
elements
&&
elements
[
0
])
{
this
.
setting
sTreeRenderer
.
showContextMenu
(
elements
[
0
],
settingDOMElement
);
this
.
setting
Renderers
.
showContextMenu
(
elements
[
0
],
settingDOMElement
);
}
}
...
...
@@ -414,9 +427,9 @@ export class SettingsEditor2 extends BaseEditor {
this
.
settingsTree
.
reveal
(
elements
[
0
],
sourceTop
);
const
domElements
=
this
.
setting
sTreeRenderer
.
getDOMElementsForSettingKey
(
this
.
settingsTree
.
getHTMLElement
(),
evt
.
targetKey
);
const
domElements
=
this
.
setting
Renderers
.
getDOMElementsForSettingKey
(
this
.
settingsTree
.
getHTMLElement
(),
evt
.
targetKey
);
if
(
domElements
&&
domElements
[
0
])
{
const
control
=
domElements
[
0
].
querySelector
(
Settings
Renderer
.
CONTROL_SELECTOR
);
const
control
=
domElements
[
0
].
querySelector
(
AbstractSetting
Renderer
.
CONTROL_SELECTOR
);
if
(
control
)
{
(
<
HTMLElement
>
control
).
focus
();
}
...
...
@@ -483,57 +496,57 @@ export class SettingsEditor2 extends BaseEditor {
this
.
createTOC
(
bodyContainer
);
this
.
createFocusSink
(
bodyContainer
,
e
=>
{
if
(
DOM
.
findParentWithClass
(
e
.
relatedTarget
,
'
settings-editor-tree
'
))
{
if
(
this
.
settingsTree
.
getScrollPosition
()
>
0
)
{
const
firstElement
=
this
.
settingsTree
.
getFirstVisibleElement
();
this
.
settingsTree
.
reveal
(
firstElement
,
0.1
);
return
true
;
}
}
else
{
const
firstControl
=
this
.
settingsTree
.
getHTMLElement
().
querySelector
(
SettingsRenderer
.
CONTROL_SELECTOR
);
if
(
firstControl
)
{
(
<
HTMLElement
>
firstControl
).
focus
();
}
}
return
false
;
},
'
settings list focus helper
'
);
//
this.createFocusSink(
//
bodyContainer,
//
e => {
//
if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) {
//
if (this.settingsTree.getScrollPosition() > 0) {
//
const firstElement = this.settingsTree.getFirstVisibleElement();
//
this.settingsTree.reveal(firstElement, 0.1);
//
return true;
//
}
//
} else {
//
const firstControl = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR);
//
if (firstControl) {
//
(<HTMLElement>firstControl).focus();
//
}
//
}
//
return false;
//
},
//
'settings list focus helper');
this
.
createSettingsTree
(
bodyContainer
);
this
.
createFocusSink
(
bodyContainer
,
e
=>
{
if
(
DOM
.
findParentWithClass
(
e
.
relatedTarget
,
'
settings-editor-tree
'
))
{
if
(
this
.
settingsTree
.
getScrollPosition
()
<
1
)
{
const
lastElement
=
this
.
settingsTree
.
getLastVisibleElement
();
this
.
settingsTree
.
reveal
(
lastElement
,
0.9
);
return
true
;
}
}
return
false
;
},
'
settings list focus helper
'
);
//
this.createFocusSink(
//
bodyContainer,
//
e => {
//
if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) {
//
if (this.settingsTree.getScrollPosition() < 1) {
//
const lastElement = this.settingsTree.getLastVisibleElement();
//
this.settingsTree.reveal(lastElement, 0.9);
//
return true;
//
}
//
}
//
return false;
//
},
//
'settings list focus helper'
//
);
}
private
createFocusSink
(
container
:
HTMLElement
,
callback
:
(
e
:
any
)
=>
boolean
,
label
:
string
):
HTMLElement
{
const
listFocusSink
=
DOM
.
append
(
container
,
$
(
'
.settings-tree-focus-sink
'
));
listFocusSink
.
setAttribute
(
'
aria-label
'
,
label
);
listFocusSink
.
tabIndex
=
0
;
this
.
_register
(
DOM
.
addDisposableListener
(
listFocusSink
,
'
focus
'
,
(
e
:
any
)
=>
{
if
(
e
.
relatedTarget
&&
callback
(
e
))
{
e
.
relatedTarget
.
focus
();
}
}));
//
private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement {
//
const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink'));
//
listFocusSink.setAttribute('aria-label', label);
//
listFocusSink.tabIndex = 0;
//
this._register(DOM.addDisposableListener(listFocusSink, 'focus', (e: any) => {
//
if (e.relatedTarget && callback(e)) {
//
e.relatedTarget.focus();
//
}
//
}));
return
listFocusSink
;
}
//
return listFocusSink;
//
}
private
createTOC
(
parent
:
HTMLElement
):
void
{
this
.
tocTreeModel
=
new
TOCTreeModel
(
this
.
viewState
);
...
...
@@ -553,15 +566,9 @@ export class SettingsEditor2 extends BaseEditor {
if
(
this
.
searchResultModel
)
{
this
.
viewState
.
filterToCategory
=
element
;
this
.
renderTree
();
}
if
(
element
&&
(
!
e
.
payload
||
!
e
.
payload
.
fromScroll
))
{
let
refreshP
:
Promise
<
void
>
=
Promise
.
resolve
(
null
);
if
(
this
.
settingsTreeDataSource
.
pageTo
(
element
.
index
,
true
))
{
refreshP
=
this
.
renderTree
();
}
refreshP
.
then
(()
=>
this
.
settingsTree
.
reveal
(
element
,
0
));
this
.
settingsTree
.
scrollTop
=
0
;
}
else
if
(
element
&&
(
!
e
.
payload
||
!
e
.
payload
.
fromScroll
))
{
this
.
settingsTree
.
reveal
(
element
,
0
);
}
}));
...
...
@@ -585,17 +592,17 @@ export class SettingsEditor2 extends BaseEditor {
labelDiv
.
id
=
'
settings_aria_more_actions_shortcut_label
'
;
labelDiv
.
setAttribute
(
'
aria-label
'
,
''
);
this
.
setting
sTreeRenderer
=
this
.
instantiationService
.
createInstance
(
SettingsRenderer
,
this
.
settingsTreeContainer
);
this
.
_register
(
this
.
setting
sTreeRenderer
.
onDidChangeSetting
(
e
=>
this
.
onDidChangeSetting
(
e
.
key
,
e
.
value
,
e
.
type
)));
this
.
_register
(
this
.
setting
sTreeRenderer
.
onDidOpenSettings
(
settingKey
=>
{
this
.
setting
Renderers
=
this
.
instantiationService
.
createInstance
(
SettingTreeRenderers
);
this
.
_register
(
this
.
setting
Renderers
.
onDidChangeSetting
(
e
=>
this
.
onDidChangeSetting
(
e
.
key
,
e
.
value
,
e
.
type
)));
this
.
_register
(
this
.
setting
Renderers
.
onDidOpenSettings
(
settingKey
=>
{
this
.
openSettingsFile
(
settingKey
);
}));
this
.
_register
(
this
.
setting
sTreeRenderer
.
onDidClickSettingLink
(
settingName
=>
this
.
onDidClickSetting
(
settingName
)));
this
.
_register
(
this
.
setting
sTreeRenderer
.
onDidFocusSetting
(
element
=>
{
this
.
_register
(
this
.
setting
Renderers
.
onDidClickSettingLink
(
settingName
=>
this
.
onDidClickSetting
(
settingName
)));
this
.
_register
(
this
.
setting
Renderers
.
onDidFocusSetting
(
element
=>
{
this
.
lastFocusedSettingElement
=
element
.
setting
.
key
;
this
.
settingsTree
.
reveal
(
element
);
}));
this
.
_register
(
this
.
setting
sTreeRenderer
.
onDidClickOverrideElement
((
element
:
ISettingOverrideClickEvent
)
=>
{
this
.
_register
(
this
.
setting
Renderers
.
onDidClickOverrideElement
((
element
:
ISettingOverrideClickEvent
)
=>
{
if
(
ConfigurationTargetToString
(
ConfigurationTarget
.
WORKSPACE
)
===
element
.
scope
.
toUpperCase
())
{
this
.
settingsTargetsWidget
.
updateTarget
(
ConfigurationTarget
.
WORKSPACE
);
}
else
if
(
ConfigurationTargetToString
(
ConfigurationTarget
.
USER
)
===
element
.
scope
.
toUpperCase
())
{
...
...
@@ -605,25 +612,19 @@ export class SettingsEditor2 extends BaseEditor {
this
.
searchWidget
.
setValue
(
element
.
targetKey
);
}));
this
.
settingsTreeDataSource
=
this
.
instantiationService
.
createInstance
(
SimplePagedDataSource
,
this
.
instantiationService
.
createInstance
(
SettingsDataSource
,
this
.
viewState
));
this
.
settingsTree
=
this
.
_register
(
this
.
instantiationService
.
createInstance
(
SettingsTree
,
this
.
settingsTreeContainer
,
this
.
viewState
,
{
renderer
:
this
.
settingsTreeRenderer
,
dataSource
:
this
.
settingsTreeDataSource
}));
this
.
settingRenderers
.
allRenderers
));
this
.
settingsTree
.
getHTMLElement
().
attributes
.
removeNamedItem
(
'
tabindex
'
);
// Have to redefine role of the tree widget to form for input elements
// TODO:CDL make this an option for tree
this
.
settingsTree
.
getHTMLElement
().
setAttribute
(
'
role
'
,
'
form
'
);
this
.
_register
(
this
.
settingsTree
.
onDidScroll
(()
=>
{
this
.
updateTreeScrollSync
();
}));
//
this._register(this.settingsTree.onDidScroll(() => {
//
this.updateTreeScrollSync();
//
}));
}
private
notifyNoSaveNeeded
()
{
...
...
@@ -649,7 +650,7 @@ export class SettingsEditor2 extends BaseEditor {
}
private
updateTreeScrollSync
():
void
{
this
.
setting
sTreeRenderer
.
cancelSuggesters
();
this
.
setting
Renderers
.
cancelSuggesters
();
if
(
this
.
searchResultModel
)
{
return
;
}
...
...
@@ -657,36 +658,38 @@ export class SettingsEditor2 extends BaseEditor {
if
(
!
this
.
tocTree
.
getInput
())
{
return
;
}
this
.
updateTreePagingByScroll
();
const
elementToSync
=
this
.
settingsTree
.
getFirstVisibleElement
();
const
element
=
elementToSync
instanceof
SettingsTreeSettingElement
?
elementToSync
.
parent
:
elementToSync
instanceof
SettingsTreeGroupElement
?
elementToSync
:
null
;
// this.updateTreePagingByScroll();
const
element
=
this
.
tocTreeModel
.
children
[
0
];
// const elementToSync = this.settingsTree.getFirstVisibleElement();
// const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent :
// elementToSync instanceof SettingsTreeGroupElement ? elementToSync :
// null;
if
(
element
&&
this
.
tocTree
.
getSelection
()[
0
]
!==
element
)
{
this
.
tocTree
.
reveal
(
element
);
const
elementTop
=
this
.
tocTree
.
getRelativeTop
(
element
);
collapseAll
(
this
.
tocTree
,
element
);
if
(
elementTop
<
0
||
elementTop
>
1
)
{
this
.
tocTree
.
reveal
(
element
);
}
else
{
this
.
tocTree
.
reveal
(
element
,
elementTop
);
}
//
this.tocTree.reveal(element);
//
const elementTop = this.tocTree.getRelativeTop(element);
//
collapseAll(this.tocTree, element);
//
if (elementTop < 0 || elementTop > 1) {
//
this.tocTree.reveal(element);
//
} else {
//
this.tocTree.reveal(element, elementTop);
//
}
this
.
tocTree
.
expand
(
element
);
//
this.tocTree.expand(element);
this
.
tocTree
.
setSelection
([
element
]);
this
.
tocTree
.
setFocus
(
element
,
{
fromScroll
:
true
});
}
}
private
updateTreePagingByScroll
():
void
{
const
lastVisibleElement
=
this
.
settingsTree
.
getLastVisibleElement
();
if
(
lastVisibleElement
&&
this
.
settingsTreeDataSource
.
pageTo
(
lastVisibleElement
.
index
))
{
this
.
renderTree
();
}
}
//
private updateTreePagingByScroll(): void {
//
const lastVisibleElement = this.settingsTree.getLastVisibleElement();
//
if (lastVisibleElement && this.settingsTreeDataSource.pageTo(lastVisibleElement.index)) {
//
this.renderTree();
//
}
//
}
private
updateChangedSetting
(
key
:
string
,
value
:
any
):
Promise
<
void
>
{
// ConfigurationService displays the error if this fails.
...
...
@@ -800,12 +803,6 @@ export class SettingsEditor2 extends BaseEditor {
if
(
this
.
configurationService
.
getValue
(
'
workbench.settings.settingsSearchTocBehavior
'
)
===
'
hide
'
)
{
DOM
.
toggleClass
(
this
.
rootElement
,
'
no-toc-search
'
,
!!
this
.
searchResultModel
);
}
if
(
this
.
searchResultModel
)
{
this
.
settingsTreeDataSource
.
pageTo
(
Number
.
MAX_VALUE
);
}
else
{
this
.
settingsTreeDataSource
.
reset
();
}
}
private
scheduleRefresh
(
element
:
HTMLElement
,
key
=
''
):
void
{
...
...
@@ -870,7 +867,7 @@ export class SettingsEditor2 extends BaseEditor {
}
else
{
this
.
settingsTreeModel
=
this
.
instantiationService
.
createInstance
(
SettingsTreeModel
,
this
.
viewState
);
this
.
settingsTreeModel
.
update
(
resolvedSettingsRoot
);
this
.
settingsTree
.
setInput
(
this
.
settingsTreeModel
.
root
);
this
.
refreshTree
(
);
this
.
tocTreeModel
.
settingsTreeRoot
=
this
.
settingsTreeModel
.
root
as
SettingsTreeGroupElement
;
if
(
this
.
tocTree
.
getInput
())
{
...
...
@@ -914,11 +911,11 @@ export class SettingsEditor2 extends BaseEditor {
}
// If a setting control is currently focused, schedule a refresh for later
const
focusedSetting
=
this
.
setting
sTreeRenderer
.
getSettingDOMElementForDOMElement
(
this
.
getActiveElementInSettingsTree
());
const
focusedSetting
=
this
.
setting
Renderers
.
getSettingDOMElementForDOMElement
(
this
.
getActiveElementInSettingsTree
());
if
(
focusedSetting
&&
!
force
)
{
// If a single setting is being refreshed, it's ok to refresh now if that is not the focused setting
if
(
key
)
{
const
focusedKey
=
focusedSetting
.
getAttribute
(
Settings
Renderer
.
SETTING_KEY_ATTR
);
const
focusedKey
=
focusedSetting
.
getAttribute
(
AbstractSetting
Renderer
.
SETTING_KEY_ATTR
);
if
(
focusedKey
===
key
&&
!
DOM
.
hasClass
(
focusedSetting
,
'
setting-item-exclude
'
))
{
// update `exclude`s live, as they have a separate "submit edit" step built in before this
...
...
@@ -932,31 +929,31 @@ export class SettingsEditor2 extends BaseEditor {
}
}
let
refreshP
:
Promise
<
any
>
;
if
(
key
)
{
const
elements
=
this
.
currentSettingsModel
.
getElementsByName
(
key
);
if
(
elements
&&
elements
.
length
)
{
// TODO https://github.com/Microsoft/vscode/issues/57360
// refreshP = Promise.all(elements.map(e => this.settingsTree.refresh(e)));
refreshP
=
this
.
settingsTree
.
refresh
();
this
.
refreshTree
();
}
else
{
// Refresh requested for a key that we don't know about
return
Promise
.
resolve
(
null
);
}
}
else
{
refreshP
=
this
.
settingsTree
.
refresh
();
this
.
refreshTree
();
}
return
refreshP
.
then
(()
=>
{
this
.
tocTreeModel
.
update
();
return
this
.
tocTree
.
refresh
();
}).
then
(()
=>
{
});
this
.
tocTreeModel
.
update
();
return
this
.
tocTree
.
refresh
();
}
private
refreshTree
():
void
{
this
.
settingsTree
.
setChildren
(
null
,
createGroupIterator
(
this
.
currentSettingsModel
.
root
));
}
private
updateModifiedLabelForKey
(
key
:
string
):
void
{
const
dataElements
=
this
.
currentSettingsModel
.
getElementsByName
(
key
);
const
isModified
=
dataElements
&&
dataElements
[
0
]
&&
dataElements
[
0
].
isConfigured
;
// all elements are either configured or not
const
elements
=
this
.
setting
sTreeRenderer
.
getDOMElementsForSettingKey
(
this
.
settingsTree
.
getHTMLElement
(),
key
);
const
elements
=
this
.
setting
Renderers
.
getDOMElementsForSettingKey
(
this
.
settingsTree
.
getHTMLElement
(),
key
);
if
(
elements
&&
elements
[
0
])
{
DOM
.
toggleClass
(
elements
[
0
],
'
is-configured
'
,
isModified
);
}
...
...
@@ -1012,15 +1009,17 @@ export class SettingsEditor2 extends BaseEditor {
// Added a filter model
this
.
tocTree
.
setSelection
([]);
expandAll
(
this
.
tocTree
);
return
this
.
settingsTree
.
setInput
(
this
.
searchResultModel
.
root
).
then
(()
=>
{
this
.
renderResultCountMessages
();
});
this
.
refreshTree
();
this
.
renderResultCountMessages
();
}
else
{
// Leaving search mode
collapseAll
(
this
.
tocTree
);
return
this
.
settingsTree
.
setInput
(
this
.
settingsTreeModel
.
root
).
then
(()
=>
this
.
renderResultCountMessages
());
this
.
refreshTree
();
this
.
renderResultCountMessages
();
}
}
return
Promise
.
resolve
(
null
);
}
/**
...
...
@@ -1138,7 +1137,7 @@ export class SettingsEditor2 extends BaseEditor {
this
.
searchResultModel
.
setResult
(
type
,
result
);
this
.
tocTreeModel
.
currentSearchModel
=
this
.
searchResultModel
;
this
.
onSearchModeToggled
();
this
.
settingsTree
.
setInput
(
this
.
searchResultModel
.
root
);
this
.
refreshTree
(
);
}
else
{
this
.
searchResultModel
.
setResult
(
type
,
result
);
this
.
tocTreeModel
.
update
();
...
...
@@ -1153,7 +1152,7 @@ export class SettingsEditor2 extends BaseEditor {
}
private
renderResultCountMessages
()
{
if
(
!
this
.
settingsTree
.
getInput
()
)
{
if
(
!
this
.
currentSettingsModel
)
{
return
;
}
...
...
@@ -1201,13 +1200,12 @@ export class SettingsEditor2 extends BaseEditor {
const
listHeight
=
dimension
.
height
-
(
76
+
11
/* header height + padding*/
);
const
settingsTreeHeight
=
listHeight
-
14
;
this
.
settingsTreeContainer
.
style
.
height
=
`
${
settingsTreeHeight
}
px`
;
this
.
settingsTree
.
layout
(
settingsTreeHeight
,
800
);
this
.
settingsTree
.
layout
(
settingsTreeHeight
);
this
.
settingsTree
.
layoutWidth
(
dimension
.
width
);
const
tocTreeHeight
=
listHeight
-
16
;
this
.
tocTreeContainer
.
style
.
height
=
`
${
tocTreeHeight
}
px`
;
this
.
tocTree
.
layout
(
tocTreeHeight
,
175
);
this
.
settingsTreeRenderer
.
updateWidth
(
dimension
.
width
);
}
protected
saveState
():
void
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录