Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
掘金者说
vscode
提交
1d1105e9
V
vscode
项目概览
掘金者说
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
1d1105e9
编写于
9月 17, 2018
作者:
R
Rachel Macfarlane
提交者:
GitHub
9月 17, 2018
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add support for editing comments, #58078
上级
ec058d3d
变更
9
隐藏空白更改
内联
并排
Showing
9 changed file
with
444 addition
and
107 deletion
+444
-107
src/vs/editor/common/modes.ts
src/vs/editor/common/modes.ts
+2
-2
src/vs/vscode.proposed.d.ts
src/vs/vscode.proposed.d.ts
+55
-0
src/vs/workbench/api/electron-browser/mainThreadComments.ts
src/vs/workbench/api/electron-browser/mainThreadComments.ts
+3
-0
src/vs/workbench/api/node/extHost.protocol.ts
src/vs/workbench/api/node/extHost.protocol.ts
+1
-0
src/vs/workbench/api/node/extHostComments.ts
src/vs/workbench/api/node/extHostComments.ts
+32
-17
src/vs/workbench/parts/comments/electron-browser/commentNode.ts
.../workbench/parts/comments/electron-browser/commentNode.ts
+244
-0
src/vs/workbench/parts/comments/electron-browser/commentService.ts
...rkbench/parts/comments/electron-browser/commentService.ts
+12
-1
src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts
...ch/parts/comments/electron-browser/commentThreadWidget.ts
+31
-64
src/vs/workbench/parts/comments/electron-browser/media/review.css
...orkbench/parts/comments/electron-browser/media/review.css
+64
-23
未找到文件。
src/vs/editor/common/modes.ts
浏览文件 @
1d1105e9
...
...
@@ -1008,6 +1008,7 @@ export interface Comment {
readonly
body
:
IMarkdownString
;
readonly
userName
:
string
;
readonly
gravatar
:
string
;
readonly
canEdit
?:
boolean
;
readonly
command
?:
Command
;
}
...
...
@@ -1039,6 +1040,7 @@ export interface DocumentCommentProvider {
provideDocumentComments
(
resource
:
URI
,
token
:
CancellationToken
):
Promise
<
CommentInfo
>
;
createNewCommentThread
(
resource
:
URI
,
range
:
Range
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
CommentThread
>
;
replyToCommentThread
(
resource
:
URI
,
range
:
Range
,
thread
:
CommentThread
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
CommentThread
>
;
editComment
(
resource
:
URI
,
comment
:
Comment
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
Comment
>
;
onDidChangeCommentThreads
():
Event
<
CommentThreadChangedEvent
>
;
}
...
...
@@ -1047,8 +1049,6 @@ export interface DocumentCommentProvider {
*/
export
interface
WorkspaceCommentProvider
{
provideWorkspaceComments
(
token
:
CancellationToken
):
Promise
<
CommentThread
[]
>
;
createNewCommentThread
(
resource
:
URI
,
range
:
Range
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
CommentThread
>
;
replyToCommentThread
(
resource
:
URI
,
range
:
Range
,
thread
:
CommentThread
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
CommentThread
>
;
onDidChangeCommentThreads
():
Event
<
CommentThreadChangedEvent
>
;
}
...
...
src/vs/vscode.proposed.d.ts
浏览文件 @
1d1105e9
...
...
@@ -656,10 +656,37 @@ declare module 'vscode' {
}
interface
Comment
{
/**
* The id of the comment
*/
commentId
:
string
;
/**
* The text of the comment
*/
body
:
MarkdownString
;
/**
* The display name of the user who created the comment
*/
userName
:
string
;
/**
* The avatar src of the user who created the comment
*/
gravatar
:
string
;
/**
* Whether the current user has permission to edit the comment.
*
* This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or
* if it is provided by a `DocumentCommentProvider` and no `editComment` method is given.
*/
canEdit
?:
boolean
;
/**
* The command to be executed if the comment is selected in the Comments Panel
*/
command
?:
Command
;
}
...
...
@@ -681,14 +708,42 @@ declare module 'vscode' {
}
interface
DocumentCommentProvider
{
/**
* Provide the commenting ranges and comment threads for the given document. The comments are displayed within the editor.
*/
provideDocumentComments
(
document
:
TextDocument
,
token
:
CancellationToken
):
Promise
<
CommentInfo
>
;
/**
* Called when a user adds a new comment thread in the document at the specified range, with body text.
*/
createNewCommentThread
(
document
:
TextDocument
,
range
:
Range
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
CommentThread
>
;
/**
* Called when a user replies to a new comment thread in the document at the specified range, with body text.
*/
replyToCommentThread
(
document
:
TextDocument
,
range
:
Range
,
commentThread
:
CommentThread
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
CommentThread
>
;
/**
* Called when a user edits the comment body to the be new text text.
*/
editComment
?(
document
:
TextDocument
,
comment
:
Comment
,
text
:
string
,
token
:
CancellationToken
):
Promise
<
Comment
>
;
/**
* Notify of updates to comment threads.
*/
onDidChangeCommentThreads
:
Event
<
CommentThreadChangedEvent
>
;
}
interface
WorkspaceCommentProvider
{
/**
* Provide all comments for the workspace. Comments are shown within the comments panel. Selecting a comment
* from the panel runs the comment's command.
*/
provideWorkspaceComments
(
token
:
CancellationToken
):
Promise
<
CommentThread
[]
>
;
/**
* Notify of updates to comment threads.
*/
onDidChangeCommentThreads
:
Event
<
CommentThreadChangedEvent
>
;
}
...
...
src/vs/workbench/api/electron-browser/mainThreadComments.ts
浏览文件 @
1d1105e9
...
...
@@ -77,6 +77,9 @@ export class MainThreadComments extends Disposable implements MainThreadComments
},
replyToCommentThread
:
async
(
uri
,
range
,
thread
,
text
,
token
)
=>
{
return
this
.
_proxy
.
$replyToCommentThread
(
handle
,
uri
,
range
,
thread
,
text
);
},
editComment
:
async
(
uri
,
comment
,
text
,
token
)
=>
{
return
this
.
_proxy
.
$editComment
(
handle
,
uri
,
comment
,
text
);
}
}
);
...
...
src/vs/workbench/api/node/extHost.protocol.ts
浏览文件 @
1d1105e9
...
...
@@ -995,6 +995,7 @@ export interface ExtHostCommentsShape {
$provideDocumentComments
(
handle
:
number
,
document
:
UriComponents
):
Thenable
<
modes
.
CommentInfo
>
;
$createNewCommentThread
(
handle
:
number
,
document
:
UriComponents
,
range
:
IRange
,
text
:
string
):
Thenable
<
modes
.
CommentThread
>
;
$replyToCommentThread
(
handle
:
number
,
document
:
UriComponents
,
range
:
IRange
,
commentThread
:
modes
.
CommentThread
,
text
:
string
):
Thenable
<
modes
.
CommentThread
>
;
$editComment
(
handle
:
number
,
document
:
UriComponents
,
comment
:
modes
.
Comment
,
text
:
string
):
Thenable
<
modes
.
Comment
>
;
$provideWorkspaceComments
(
handle
:
number
):
Thenable
<
modes
.
CommentThread
[]
>
;
}
...
...
src/vs/workbench/api/node/extHostComments.ts
浏览文件 @
1d1105e9
...
...
@@ -73,10 +73,10 @@ export class ExtHostComments implements ExtHostCommentsShape {
return
TPromise
.
as
(
null
);
}
const
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
asThenable
(()
=>
{
let
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
provider
.
createNewCommentThread
(
data
.
document
,
ran
,
text
,
CancellationToken
.
None
);
}).
then
(
commentThread
=>
commentThread
?
convertToCommentThread
(
commentThread
,
this
.
_commandsConverter
)
:
null
);
}).
then
(
commentThread
=>
commentThread
?
convertToCommentThread
(
provider
,
commentThread
,
this
.
_commandsConverter
)
:
null
);
}
$replyToCommentThread
(
handle
:
number
,
uri
:
UriComponents
,
range
:
IRange
,
thread
:
modes
.
CommentThread
,
text
:
string
):
Thenable
<
modes
.
CommentThread
>
{
...
...
@@ -87,10 +87,23 @@ export class ExtHostComments implements ExtHostCommentsShape {
return
TPromise
.
as
(
null
);
}
const
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
asThenable
(()
=>
{
let
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
provider
.
replyToCommentThread
(
data
.
document
,
ran
,
convertFromCommentThread
(
thread
),
text
,
CancellationToken
.
None
);
}).
then
(
commentThread
=>
commentThread
?
convertToCommentThread
(
commentThread
,
this
.
_commandsConverter
)
:
null
);
}).
then
(
commentThread
=>
commentThread
?
convertToCommentThread
(
provider
,
commentThread
,
this
.
_commandsConverter
)
:
null
);
}
$editComment
(
handle
:
number
,
uri
:
UriComponents
,
comment
:
modes
.
Comment
,
text
:
string
):
Thenable
<
modes
.
Comment
>
{
const
data
=
this
.
_documents
.
getDocumentData
(
URI
.
revive
(
uri
));
if
(
!
data
||
!
data
.
document
)
{
throw
new
Error
(
'
Unable to retrieve document from URI
'
);
}
const
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
asThenable
(()
=>
{
return
provider
.
editComment
(
data
.
document
,
convertFromComment
(
comment
),
text
,
CancellationToken
.
None
);
}).
then
(
comment
=>
convertToComment
(
provider
,
comment
,
this
.
_commandsConverter
));
}
$provideDocumentComments
(
handle
:
number
,
uri
:
UriComponents
):
Thenable
<
modes
.
CommentInfo
>
{
...
...
@@ -99,11 +112,10 @@ export class ExtHostComments implements ExtHostCommentsShape {
return
TPromise
.
as
(
null
);
}
const
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
asThenable
(()
=>
{
let
provider
=
this
.
_documentProviders
.
get
(
handle
);
return
provider
.
provideDocumentComments
(
data
.
document
,
CancellationToken
.
None
);
})
.
then
(
commentInfo
=>
commentInfo
?
convertCommentInfo
(
handle
,
commentInfo
,
this
.
_commandsConverter
)
:
null
);
}).
then
(
commentInfo
=>
commentInfo
?
convertCommentInfo
(
handle
,
provider
,
commentInfo
,
this
.
_commandsConverter
)
:
null
);
}
$provideWorkspaceComments
(
handle
:
number
):
Thenable
<
modes
.
CommentThread
[]
>
{
...
...
@@ -115,7 +127,7 @@ export class ExtHostComments implements ExtHostCommentsShape {
return
asThenable
(()
=>
{
return
provider
.
provideWorkspaceComments
(
CancellationToken
.
None
);
}).
then
(
comments
=>
comments
.
map
(
x
=>
convertToCommentThread
(
x
,
this
.
_commandsConverter
)
comments
.
map
(
comment
=>
convertToCommentThread
(
provider
,
comment
,
this
.
_commandsConverter
)
));
}
...
...
@@ -124,28 +136,28 @@ export class ExtHostComments implements ExtHostCommentsShape {
this
.
_proxy
.
$onDidCommentThreadsChange
(
handle
,
{
owner
:
handle
,
changed
:
event
.
changed
.
map
(
x
=>
convertToCommentThread
(
x
,
this
.
_commandsConverter
)),
added
:
event
.
added
.
map
(
x
=>
convertToCommentThread
(
x
,
this
.
_commandsConverter
)),
removed
:
event
.
removed
.
map
(
x
=>
convertToCommentThread
(
x
,
this
.
_commandsConverter
))
changed
:
event
.
changed
.
map
(
thread
=>
convertToCommentThread
(
provider
,
thread
,
this
.
_commandsConverter
)),
added
:
event
.
added
.
map
(
thread
=>
convertToCommentThread
(
provider
,
thread
,
this
.
_commandsConverter
)),
removed
:
event
.
removed
.
map
(
thread
=>
convertToCommentThread
(
provider
,
thread
,
this
.
_commandsConverter
))
});
});
}
}
function
convertCommentInfo
(
owner
:
number
,
vscodeCommentInfo
:
vscode
.
CommentInfo
,
commandsConverter
:
CommandsConverter
):
modes
.
CommentInfo
{
function
convertCommentInfo
(
owner
:
number
,
provider
:
vscode
.
DocumentCommentProvider
,
vscodeCommentInfo
:
vscode
.
CommentInfo
,
commandsConverter
:
CommandsConverter
):
modes
.
CommentInfo
{
return
{
owner
:
owner
,
threads
:
vscodeCommentInfo
.
threads
.
map
(
x
=>
convertToCommentThread
(
x
,
commandsConverter
)),
threads
:
vscodeCommentInfo
.
threads
.
map
(
x
=>
convertToCommentThread
(
provider
,
x
,
commandsConverter
)),
commentingRanges
:
vscodeCommentInfo
.
commentingRanges
?
vscodeCommentInfo
.
commentingRanges
.
map
(
range
=>
extHostTypeConverter
.
Range
.
from
(
range
))
:
[]
};
}
function
convertToCommentThread
(
vscodeCommentThread
:
vscode
.
CommentThread
,
commandsConverter
:
CommandsConverter
):
modes
.
CommentThread
{
function
convertToCommentThread
(
provider
:
vscode
.
DocumentCommentProvider
|
vscode
.
WorkspaceCommentProvider
,
vscodeCommentThread
:
vscode
.
CommentThread
,
commandsConverter
:
CommandsConverter
):
modes
.
CommentThread
{
return
{
threadId
:
vscodeCommentThread
.
threadId
,
resource
:
vscodeCommentThread
.
resource
.
toString
(),
range
:
extHostTypeConverter
.
Range
.
from
(
vscodeCommentThread
.
range
),
comments
:
vscodeCommentThread
.
comments
.
map
(
comment
=>
convertToComment
(
comment
,
commandsConverter
)),
comments
:
vscodeCommentThread
.
comments
.
map
(
comment
=>
convertToComment
(
provider
,
comment
,
commandsConverter
)),
collapsibleState
:
vscodeCommentThread
.
collapsibleState
};
}
...
...
@@ -165,16 +177,19 @@ function convertFromComment(comment: modes.Comment): vscode.Comment {
commentId
:
comment
.
commentId
,
body
:
extHostTypeConverter
.
MarkdownString
.
to
(
comment
.
body
),
userName
:
comment
.
userName
,
gravatar
:
comment
.
gravatar
gravatar
:
comment
.
gravatar
,
canEdit
:
comment
.
canEdit
};
}
function
convertToComment
(
vscodeComment
:
vscode
.
Comment
,
commandsConverter
:
CommandsConverter
):
modes
.
Comment
{
function
convertToComment
(
provider
:
vscode
.
DocumentCommentProvider
|
vscode
.
WorkspaceCommentProvider
,
vscodeComment
:
vscode
.
Comment
,
commandsConverter
:
CommandsConverter
):
modes
.
Comment
{
const
canEdit
=
!!
(
provider
as
vscode
.
DocumentCommentProvider
).
editComment
&&
vscodeComment
.
canEdit
;
return
{
commentId
:
vscodeComment
.
commentId
,
body
:
extHostTypeConverter
.
MarkdownString
.
from
(
vscodeComment
.
body
),
userName
:
vscodeComment
.
userName
,
gravatar
:
vscodeComment
.
gravatar
,
canEdit
:
canEdit
,
command
:
vscodeComment
.
command
?
commandsConverter
.
toInternal
(
vscodeComment
.
command
)
:
null
};
}
src/vs/workbench/parts/comments/electron-browser/commentNode.ts
0 → 100644
浏览文件 @
1d1105e9
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'
use strict
'
;
import
*
as
nls
from
'
vs/nls
'
;
import
*
as
dom
from
'
vs/base/browser/dom
'
;
import
*
as
modes
from
'
vs/editor/common/modes
'
;
import
{
IKeyboardEvent
}
from
'
vs/base/browser/keyboardEvent
'
;
import
{
ActionBar
}
from
'
vs/base/browser/ui/actionbar/actionbar
'
;
import
{
Button
}
from
'
vs/base/browser/ui/button/button
'
;
import
{
Action
}
from
'
vs/base/common/actions
'
;
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
ITextModel
}
from
'
vs/editor/common/model
'
;
import
{
IModelService
}
from
'
vs/editor/common/services/modelService
'
;
import
{
IModeService
}
from
'
vs/editor/common/services/modeService
'
;
import
{
MarkdownRenderer
}
from
'
vs/editor/contrib/markdown/markdownRenderer
'
;
import
{
IInstantiationService
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
inputValidationErrorBorder
}
from
'
vs/platform/theme/common/colorRegistry
'
;
import
{
attachButtonStyler
}
from
'
vs/platform/theme/common/styler
'
;
import
{
IThemeService
}
from
'
vs/platform/theme/common/themeService
'
;
import
{
ICommentService
}
from
'
vs/workbench/parts/comments/electron-browser/commentService
'
;
import
{
SimpleCommentEditor
}
from
'
vs/workbench/parts/comments/electron-browser/simpleCommentEditor
'
;
import
{
KeyCode
}
from
'
vs/base/common/keyCodes
'
;
import
{
isMacintosh
}
from
'
vs/base/common/platform
'
;
import
{
Selection
}
from
'
vs/editor/common/core/selection
'
;
const
UPDATE_COMMENT_LABEL
=
nls
.
localize
(
'
label.updateComment
'
,
"
Update comment
"
);
const
UPDATE_IN_PROGRESS_LABEL
=
nls
.
localize
(
'
label.updatingComment
'
,
"
Updating comment...
"
);
export
class
CommentNode
extends
Disposable
{
private
_domNode
:
HTMLElement
;
private
_body
:
HTMLElement
;
private
_md
:
HTMLElement
;
private
_clearTimeout
:
any
;
private
_editAction
:
Action
;
private
_commentEditContainer
:
HTMLElement
;
private
_commentEditor
:
SimpleCommentEditor
;
private
_commentEditorModel
:
ITextModel
;
private
_updateCommentButton
:
Button
;
private
_errorEditingContainer
:
HTMLElement
;
public
get
domNode
():
HTMLElement
{
return
this
.
_domNode
;
}
constructor
(
public
comment
:
modes
.
Comment
,
private
owner
:
number
,
private
resource
:
URI
,
private
markdownRenderer
:
MarkdownRenderer
,
private
themeService
:
IThemeService
,
private
instantiationService
:
IInstantiationService
,
private
commentService
:
ICommentService
,
private
modelService
:
IModelService
,
private
modeService
:
IModeService
)
{
super
();
this
.
_domNode
=
dom
.
$
(
'
div.review-comment
'
);
this
.
_domNode
.
tabIndex
=
0
;
const
avatar
=
dom
.
append
(
this
.
_domNode
,
dom
.
$
(
'
div.avatar-container
'
));
const
img
=
<
HTMLImageElement
>
dom
.
append
(
avatar
,
dom
.
$
(
'
img.avatar
'
));
img
.
src
=
comment
.
gravatar
;
const
commentDetailsContainer
=
dom
.
append
(
this
.
_domNode
,
dom
.
$
(
'
.review-comment-contents
'
));
this
.
createHeader
(
commentDetailsContainer
);
this
.
_body
=
dom
.
append
(
commentDetailsContainer
,
dom
.
$
(
'
div.comment-body
'
));
this
.
_md
=
this
.
markdownRenderer
.
render
(
comment
.
body
).
element
;
this
.
_body
.
appendChild
(
this
.
_md
);
this
.
_domNode
.
setAttribute
(
'
aria-label
'
,
`
${
comment
.
userName
}
,
${
comment
.
body
.
value
}
`
);
this
.
_domNode
.
setAttribute
(
'
role
'
,
'
treeitem
'
);
this
.
_clearTimeout
=
null
;
}
private
createHeader
(
commentDetailsContainer
:
HTMLElement
):
void
{
const
header
=
dom
.
append
(
commentDetailsContainer
,
dom
.
$
(
'
div.comment-title
'
));
const
author
=
dom
.
append
(
header
,
dom
.
$
(
'
strong.author
'
));
author
.
innerText
=
this
.
comment
.
userName
;
const
actions
:
Action
[]
=
[];
if
(
this
.
comment
.
canEdit
)
{
this
.
_editAction
=
this
.
createEditAction
(
commentDetailsContainer
);
actions
.
push
(
this
.
_editAction
);
}
if
(
actions
.
length
)
{
const
actionsContainer
=
dom
.
append
(
header
,
dom
.
$
(
'
.comment-actions.hidden
'
));
const
actionBar
=
new
ActionBar
(
actionsContainer
,
{});
this
.
_toDispose
.
push
(
actionBar
);
this
.
registerActionBarListeners
(
actionsContainer
);
actions
.
forEach
(
action
=>
actionBar
.
push
(
action
,
{
label
:
false
,
icon
:
true
}));
}
}
private
createCommentEditor
():
void
{
const
container
=
dom
.
append
(
this
.
_commentEditContainer
,
dom
.
$
(
'
.edit-textarea
'
));
this
.
_commentEditor
=
this
.
instantiationService
.
createInstance
(
SimpleCommentEditor
,
container
,
SimpleCommentEditor
.
getEditorOptions
());
const
resource
=
URI
.
parse
(
`comment:commentinput-
${
this
.
comment
.
commentId
}
-
${
Date
.
now
()}
.md`
);
this
.
_commentEditorModel
=
this
.
modelService
.
createModel
(
''
,
this
.
modeService
.
getOrCreateModeByFilenameOrFirstLine
(
resource
.
path
),
resource
,
true
);
this
.
_commentEditor
.
setModel
(
this
.
_commentEditorModel
);
this
.
_commentEditor
.
setValue
(
this
.
comment
.
body
.
value
);
this
.
_commentEditor
.
layout
({
width
:
container
.
clientWidth
-
14
,
height
:
90
});
this
.
_commentEditor
.
focus
();
const
lastLine
=
this
.
_commentEditorModel
.
getLineCount
();
const
lastColumn
=
this
.
_commentEditorModel
.
getLineContent
(
lastLine
).
length
+
1
;
this
.
_commentEditor
.
setSelection
(
new
Selection
(
lastLine
,
lastColumn
,
lastLine
,
lastColumn
));
this
.
_toDispose
.
push
(
this
.
_commentEditor
.
onKeyDown
((
e
:
IKeyboardEvent
)
=>
{
const
isCmdOrCtrl
=
isMacintosh
?
e
.
metaKey
:
e
.
ctrlKey
;
if
(
this
.
_updateCommentButton
.
enabled
&&
e
.
keyCode
===
KeyCode
.
Enter
&&
isCmdOrCtrl
)
{
this
.
editComment
();
}
}));
this
.
_toDispose
.
push
(
this
.
_commentEditor
);
this
.
_toDispose
.
push
(
this
.
_commentEditorModel
);
}
private
removeCommentEditor
()
{
this
.
_editAction
.
enabled
=
true
;
this
.
_body
.
classList
.
remove
(
'
hidden
'
);
this
.
_commentEditorModel
.
dispose
();
this
.
_commentEditor
.
dispose
();
this
.
_commentEditor
=
null
;
this
.
_commentEditContainer
.
remove
();
}
private
editComment
():
void
{
this
.
_updateCommentButton
.
enabled
=
false
;
this
.
_updateCommentButton
.
label
=
UPDATE_IN_PROGRESS_LABEL
;
try
{
this
.
commentService
.
editComment
(
this
.
owner
,
this
.
resource
,
this
.
comment
,
this
.
_commentEditor
.
getValue
()).
then
(
editedComment
=>
{
this
.
_updateCommentButton
.
enabled
=
true
;
this
.
_updateCommentButton
.
label
=
UPDATE_COMMENT_LABEL
;
this
.
_commentEditor
.
getDomNode
().
style
.
outline
=
''
;
this
.
removeCommentEditor
();
this
.
update
(
editedComment
);
});
}
catch
(
e
)
{
this
.
_updateCommentButton
.
enabled
=
true
;
this
.
_updateCommentButton
.
label
=
UPDATE_COMMENT_LABEL
;
this
.
_commentEditor
.
getDomNode
().
style
.
outline
=
`1px solid
${
this
.
themeService
.
getTheme
().
getColor
(
inputValidationErrorBorder
)}
`
;
this
.
_errorEditingContainer
.
textContent
=
nls
.
localize
(
'
commentCreationError
'
,
"
Updating the comment failed: {0}.
"
,
e
.
message
);
this
.
_errorEditingContainer
.
classList
.
remove
(
'
hidden
'
);
this
.
_commentEditor
.
focus
();
}
}
private
createEditAction
(
commentDetailsContainer
:
HTMLElement
):
Action
{
return
new
Action
(
'
comment.edit
'
,
nls
.
localize
(
'
label.edit
'
,
"
Edit
"
),
'
octicon octicon-pencil
'
,
true
,
()
=>
{
this
.
_body
.
classList
.
add
(
'
hidden
'
);
this
.
_commentEditContainer
=
dom
.
append
(
commentDetailsContainer
,
dom
.
$
(
'
.edit-container
'
));
this
.
createCommentEditor
();
this
.
_errorEditingContainer
=
dom
.
append
(
this
.
_commentEditContainer
,
dom
.
$
(
'
.validation-error.hidden
'
));
const
formActions
=
dom
.
append
(
this
.
_commentEditContainer
,
dom
.
$
(
'
.form-actions
'
));
const
cancelEditButton
=
new
Button
(
formActions
);
cancelEditButton
.
label
=
nls
.
localize
(
'
label.cancel
'
,
"
Cancel
"
);
attachButtonStyler
(
cancelEditButton
,
this
.
themeService
);
this
.
_toDispose
.
push
(
cancelEditButton
.
onDidClick
(
_
=>
{
this
.
removeCommentEditor
();
}));
this
.
_updateCommentButton
=
new
Button
(
formActions
);
this
.
_updateCommentButton
.
label
=
UPDATE_COMMENT_LABEL
;
attachButtonStyler
(
this
.
_updateCommentButton
,
this
.
themeService
);
this
.
_toDispose
.
push
(
this
.
_updateCommentButton
.
onDidClick
(
_
=>
{
this
.
editComment
();
}));
this
.
_toDispose
.
push
(
this
.
_commentEditor
.
onDidChangeModelContent
(
_
=>
{
this
.
_updateCommentButton
.
enabled
=
!!
this
.
_commentEditor
.
getValue
();
}));
this
.
_editAction
.
enabled
=
false
;
return
null
;
});
}
private
registerActionBarListeners
(
actionsContainer
:
HTMLElement
):
void
{
this
.
_toDispose
.
push
(
dom
.
addDisposableListener
(
this
.
_domNode
,
'
mouseenter
'
,
()
=>
{
actionsContainer
.
classList
.
remove
(
'
hidden
'
);
}));
this
.
_toDispose
.
push
(
dom
.
addDisposableListener
(
this
.
_domNode
,
'
focus
'
,
()
=>
{
actionsContainer
.
classList
.
remove
(
'
hidden
'
);
}));
this
.
_toDispose
.
push
(
dom
.
addDisposableListener
(
this
.
_domNode
,
'
mouseleave
'
,
(
e
:
MouseEvent
)
=>
{
if
(
!
this
.
_domNode
.
contains
(
document
.
activeElement
))
{
actionsContainer
.
classList
.
add
(
'
hidden
'
);
}
}));
this
.
_toDispose
.
push
(
dom
.
addDisposableListener
(
this
.
_domNode
,
'
focusout
'
,
(
e
:
FocusEvent
)
=>
{
if
(
!
this
.
_domNode
.
contains
((
<
HTMLElement
>
e
.
relatedTarget
)))
{
actionsContainer
.
classList
.
add
(
'
hidden
'
);
if
(
this
.
_commentEditor
&&
this
.
_commentEditor
.
getValue
()
===
this
.
comment
.
body
.
value
)
{
this
.
removeCommentEditor
();
}
}
}));
}
update
(
newComment
:
modes
.
Comment
)
{
if
(
newComment
.
body
!==
this
.
comment
.
body
)
{
this
.
_body
.
removeChild
(
this
.
_md
);
this
.
_md
=
this
.
markdownRenderer
.
render
(
newComment
.
body
).
element
;
this
.
_body
.
appendChild
(
this
.
_md
);
}
this
.
comment
=
newComment
;
}
focus
()
{
this
.
domNode
.
focus
();
if
(
!
this
.
_clearTimeout
)
{
dom
.
addClass
(
this
.
domNode
,
'
focus
'
);
this
.
_clearTimeout
=
setTimeout
(()
=>
{
dom
.
removeClass
(
this
.
domNode
,
'
focus
'
);
},
3000
);
}
}
dispose
()
{
this
.
_toDispose
.
forEach
(
disposeable
=>
disposeable
.
dispose
());
}
}
\ No newline at end of file
src/vs/workbench/parts/comments/electron-browser/commentService.ts
浏览文件 @
1d1105e9
...
...
@@ -5,7 +5,7 @@
'
use strict
'
;
import
{
CommentThread
,
DocumentCommentProvider
,
CommentThreadChangedEvent
,
CommentInfo
}
from
'
vs/editor/common/modes
'
;
import
{
CommentThread
,
DocumentCommentProvider
,
CommentThreadChangedEvent
,
CommentInfo
,
Comment
}
from
'
vs/editor/common/modes
'
;
import
{
createDecorator
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
Event
,
Emitter
}
from
'
vs/base/common/event
'
;
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
...
...
@@ -41,6 +41,7 @@ export interface ICommentService {
updateComments
(
event
:
CommentThreadChangedEvent
):
void
;
createNewCommentThread
(
owner
:
number
,
resource
:
URI
,
range
:
Range
,
text
:
string
):
Promise
<
CommentThread
>
;
replyToCommentThread
(
owner
:
number
,
resource
:
URI
,
range
:
Range
,
thread
:
CommentThread
,
text
:
string
):
Promise
<
CommentThread
>
;
editComment
(
owner
:
number
,
resource
:
URI
,
comment
:
Comment
,
text
:
string
):
Promise
<
Comment
>
;
getComments
(
resource
:
URI
):
Promise
<
CommentInfo
[]
>
;
}
...
...
@@ -114,6 +115,16 @@ export class CommentService extends Disposable implements ICommentService {
return
null
;
}
editComment
(
owner
:
number
,
resource
:
URI
,
comment
:
Comment
,
text
:
string
):
Promise
<
Comment
>
{
const
commentProvider
=
this
.
_commentProviders
.
get
(
owner
);
if
(
commentProvider
)
{
return
commentProvider
.
editComment
(
resource
,
comment
,
text
,
CancellationToken
.
None
);
}
return
null
;
}
getComments
(
resource
:
URI
):
Promise
<
CommentInfo
[]
>
{
const
result
=
[];
for
(
const
handle
of
keys
(
this
.
_commentProviders
))
{
...
...
src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts
浏览文件 @
1d1105e9
...
...
@@ -37,65 +37,12 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import
{
MarkdownRenderer
}
from
'
vs/editor/contrib/markdown/markdownRenderer
'
;
import
{
IMarginData
}
from
'
vs/editor/browser/controller/mouseTarget
'
;
import
{
ModelDecorationOptions
}
from
'
vs/editor/common/model/textModel
'
;
import
{
CommentNode
}
from
'
vs/workbench/parts/comments/electron-browser/commentNode
'
;
export
const
COMMENTEDITOR_DECORATION_KEY
=
'
commenteditordecoration
'
;
const
COLLAPSE_ACTION_CLASS
=
'
expand-review-action octicon octicon-x
'
;
const
COMMENT_SCHEME
=
'
comment
'
;
export
class
CommentNode
{
private
_domNode
:
HTMLElement
;
private
_body
:
HTMLElement
;
private
_md
:
HTMLElement
;
private
_clearTimeout
:
any
;
public
get
domNode
():
HTMLElement
{
return
this
.
_domNode
;
}
constructor
(
public
comment
:
modes
.
Comment
,
private
markdownRenderer
:
MarkdownRenderer
,
)
{
this
.
_domNode
=
dom
.
$
(
'
div.review-comment
'
);
this
.
_domNode
.
tabIndex
=
0
;
let
avatar
=
dom
.
append
(
this
.
_domNode
,
dom
.
$
(
'
div.avatar-container
'
));
let
img
=
<
HTMLImageElement
>
dom
.
append
(
avatar
,
dom
.
$
(
'
img.avatar
'
));
img
.
src
=
comment
.
gravatar
;
let
commentDetailsContainer
=
dom
.
append
(
this
.
_domNode
,
dom
.
$
(
'
.review-comment-contents
'
));
let
header
=
dom
.
append
(
commentDetailsContainer
,
dom
.
$
(
'
div
'
));
let
author
=
dom
.
append
(
header
,
dom
.
$
(
'
strong.author
'
));
author
.
innerText
=
comment
.
userName
;
this
.
_body
=
dom
.
append
(
commentDetailsContainer
,
dom
.
$
(
'
div.comment-body
'
));
this
.
_md
=
this
.
markdownRenderer
.
render
(
comment
.
body
).
element
;
this
.
_body
.
appendChild
(
this
.
_md
);
this
.
_domNode
.
setAttribute
(
'
aria-label
'
,
`
${
comment
.
userName
}
,
${
comment
.
body
.
value
}
`
);
this
.
_domNode
.
setAttribute
(
'
role
'
,
'
treeitem
'
);
this
.
_clearTimeout
=
null
;
}
update
(
newComment
:
modes
.
Comment
)
{
if
(
newComment
.
body
!==
this
.
comment
.
body
)
{
this
.
_body
.
removeChild
(
this
.
_md
);
this
.
_md
=
this
.
markdownRenderer
.
render
(
newComment
.
body
).
element
;
this
.
_body
.
appendChild
(
this
.
_md
);
}
this
.
comment
=
newComment
;
}
focus
()
{
this
.
domNode
.
focus
();
if
(
!
this
.
_clearTimeout
)
{
dom
.
addClass
(
this
.
domNode
,
'
focus
'
);
this
.
_clearTimeout
=
setTimeout
(()
=>
{
dom
.
removeClass
(
this
.
domNode
,
'
focus
'
);
},
3000
);
}
}
}
let
INMEM_MODEL_ID
=
0
;
export
class
ReviewZoneWidget
extends
ZoneWidget
{
...
...
@@ -219,9 +166,9 @@ export class ReviewZoneWidget extends ZoneWidget {
this
.
dispose
();
return
null
;
}
this
.
_isCollapsed
=
true
;
this
.
hide
();
return
null
;
});
...
...
@@ -265,7 +212,18 @@ export class ReviewZoneWidget extends ZoneWidget {
lastCommentElement
=
oldCommentNode
[
0
].
domNode
;
newCommentNodeList
.
unshift
(
oldCommentNode
[
0
]);
}
else
{
let
newElement
=
new
CommentNode
(
currentComment
,
this
.
_markdownRenderer
);
let
newElement
=
new
CommentNode
(
currentComment
,
this
.
owner
,
this
.
editor
.
getModel
().
uri
,
this
.
_markdownRenderer
,
this
.
themeService
,
this
.
instantiationService
,
this
.
commentService
,
this
.
modelService
,
this
.
modeService
);
this
.
_disposables
.
push
(
newElement
);
newCommentNodeList
.
unshift
(
newElement
);
if
(
lastCommentElement
)
{
this
.
_commentsElement
.
insertBefore
(
newElement
.
domNode
,
lastCommentElement
);
...
...
@@ -302,7 +260,16 @@ export class ReviewZoneWidget extends ZoneWidget {
this
.
_commentElements
=
[];
for
(
let
i
=
0
;
i
<
this
.
_commentThread
.
comments
.
length
;
i
++
)
{
let
newCommentNode
=
new
CommentNode
(
this
.
_commentThread
.
comments
[
i
],
this
.
_markdownRenderer
);
let
newCommentNode
=
new
CommentNode
(
this
.
_commentThread
.
comments
[
i
],
this
.
owner
,
this
.
editor
.
getModel
().
uri
,
this
.
_markdownRenderer
,
this
.
themeService
,
this
.
instantiationService
,
this
.
commentService
,
this
.
modelService
,
this
.
modeService
);
this
.
_disposables
.
push
(
newCommentNode
);
this
.
_commentElements
.
push
(
newCommentNode
);
this
.
_commentsElement
.
appendChild
(
newCommentNode
.
domNode
);
}
...
...
@@ -592,18 +559,18 @@ export class ReviewZoneWidget extends ZoneWidget {
const
content
:
string
[]
=
[];
const
linkColor
=
theme
.
getColor
(
textLinkForeground
);
if
(
linkColor
)
{
content
.
push
(
`.monaco-editor .review-widget .body .
review-comment
a { color:
${
linkColor
}
}`
);
content
.
push
(
`.monaco-editor .review-widget .body .
comment-body
a { color:
${
linkColor
}
}`
);
}
const
linkActiveColor
=
theme
.
getColor
(
textLinkActiveForeground
);
if
(
linkActiveColor
)
{
content
.
push
(
`.monaco-editor .review-widget .body .
review-comment
a:hover, a:active { color:
${
linkActiveColor
}
}`
);
content
.
push
(
`.monaco-editor .review-widget .body .
comment-body
a:hover, a:active { color:
${
linkActiveColor
}
}`
);
}
const
focusColor
=
theme
.
getColor
(
focusBorder
);
if
(
focusColor
)
{
content
.
push
(
`.monaco-editor .review-widget .body .
review-comment
a:focus { outline: 1px solid
${
focusColor
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .body .
comment-form .
monaco-editor.focused { outline: 1px solid
${
focusColor
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .body .
comment-body
a:focus { outline: 1px solid
${
focusColor
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .body .monaco-editor.focused { outline: 1px solid
${
focusColor
}
; }`
);
}
const
blockQuoteBackground
=
theme
.
getColor
(
textBlockQuoteBackground
);
...
...
@@ -619,17 +586,17 @@ export class ReviewZoneWidget extends ZoneWidget {
const
hcBorder
=
theme
.
getColor
(
contrastBorder
);
if
(
hcBorder
)
{
content
.
push
(
`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button { outline-color:
${
hcBorder
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .body .
comment-form .
monaco-editor { outline: 1px solid
${
hcBorder
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .body .monaco-editor { outline: 1px solid
${
hcBorder
}
; }`
);
}
const
errorBorder
=
theme
.
getColor
(
inputValidationErrorBorder
);
if
(
errorBorder
)
{
content
.
push
(
`.monaco-editor .review-widget .
body .comment-form .
validation-error { border: 1px solid
${
errorBorder
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .validation-error { border: 1px solid
${
errorBorder
}
; }`
);
}
const
errorBackground
=
theme
.
getColor
(
inputValidationErrorBackground
);
if
(
errorBackground
)
{
content
.
push
(
`.monaco-editor .review-widget .
body .comment-form .
validation-error { background:
${
errorBackground
}
; }`
);
content
.
push
(
`.monaco-editor .review-widget .validation-error { background:
${
errorBackground
}
; }`
);
}
const
errorForeground
=
theme
.
getColor
(
inputValidationErrorForeground
);
...
...
src/vs/workbench/parts/comments/electron-browser/media/review.css
浏览文件 @
1d1105e9
...
...
@@ -30,12 +30,33 @@
position
:
absolute
;
}
.monaco-editor
.review-widget
.hidden
{
display
:
none
!important
;
}
.monaco-editor
.review-widget
.body
.review-comment
{
padding
:
8px
16px
8px
20px
;
display
:
flex
;
}
.monaco-editor
.review-widget
.body
.review-comment
blockquote
{
.monaco-editor
.review-widget
.body
.review-comment
.comment-actions
{
margin-left
:
auto
;
}
.monaco-editor
.review-widget
.body
.review-comment
.comment-actions
.action-item
{
width
:
30px
;
}
.monaco-editor
.review-widget
.body
.review-comment
.comment-title
{
display
:
flex
;
width
:
100%
;
}
.monaco-editor
.review-widget
.body
.review-comment
.comment-title
.action-label.octicon
{
line-height
:
18px
;
}
.monaco-editor
.review-widget
.body
.comment-body
blockquote
{
margin
:
0
7px
0
5px
;
padding
:
0
16px
0
10px
;
border-left-width
:
5px
;
...
...
@@ -58,8 +79,10 @@
}
.monaco-editor
.review-widget
.body
.review-comment
.review-comment-contents
{
margin
-left
:
20px
;
padding
-left
:
20px
;
user-select
:
text
;
width
:
100%
;
overflow
:
hidden
;
}
.monaco-editor
.review-widget
.body
pre
{
...
...
@@ -68,13 +91,14 @@
white-space
:
pre
;
}
.monaco-editor.vs-dark
.review-widget
.body
.
review-comment
.review-comment-contents
h4
{
.monaco-editor.vs-dark
.review-widget
.body
.
comment-body
h4
{
margin
:
0
;
}
.monaco-editor.vs-dark
.review-widget
.body
.review-comment
.review-comment-contents
.author
{
color
:
#fff
;
font-weight
:
600
;
line-height
:
19px
;
}
.monaco-editor.vs-dark
.review-widget
.body
.review-comment
.review-comment-contents
.comment-body
{
...
...
@@ -85,39 +109,39 @@
color
:
#e0e0e0
;
}
.monaco-editor
.review-widget
.body
p
,
.monaco-editor
.review-widget
.body
ul
{
.monaco-editor
.review-widget
.body
.comment-body
p
,
.monaco-editor
.review-widget
.body
.comment-body
ul
{
margin
:
8px
0
;
}
.monaco-editor
.review-widget
.body
p
:first-child
,
.monaco-editor
.review-widget
.body
ul
:first-child
{
.monaco-editor
.review-widget
.body
.comment-body
p
:first-child
,
.monaco-editor
.review-widget
.body
.comment-body
ul
:first-child
{
margin-top
:
0
;
}
.monaco-editor
.review-widget
.body
p
:last-child
,
.monaco-editor
.review-widget
.body
ul
:last-child
{
.monaco-editor
.review-widget
.body
.comment-body
p
:last-child
,
.monaco-editor
.review-widget
.body
.comment-body
ul
:last-child
{
margin-bottom
:
0
;
}
.monaco-editor
.review-widget
.body
ul
{
.monaco-editor
.review-widget
.body
.comment-body
ul
{
padding-left
:
20px
;
}
.monaco-editor
.review-widget
.body
li
>
p
{
.monaco-editor
.review-widget
.body
.comment-body
li
>
p
{
margin-bottom
:
0
;
}
.monaco-editor
.review-widget
.body
li
>
ul
{
.monaco-editor
.review-widget
.body
.comment-body
li
>
ul
{
margin-top
:
0
;
}
.monaco-editor
.review-widget
.body
code
{
.monaco-editor
.review-widget
.body
.comment-body
code
{
border-radius
:
3px
;
padding
:
0
0.4em
;
}
.monaco-editor
.review-widget
.body
span
{
.monaco-editor
.review-widget
.body
.comment-body
span
{
white-space
:
pre
;
}
...
...
@@ -130,7 +154,7 @@
padding
:
8px
0
;
}
.monaco-editor
.review-widget
.
body
.comment-form
.
validation-error
{
.monaco-editor
.review-widget
.validation-error
{
display
:
inline-block
;
overflow
:
hidden
;
text-align
:
left
;
...
...
@@ -149,10 +173,6 @@
word-wrap
:
break-word
;
}
.monaco-editor
.review-widget
.body
.comment-form
.validation-error.hidden
{
display
:
none
;
}
.monaco-editor
.review-widget
.body
.comment-form.expand
.review-thread-reply-button
{
display
:
none
;
}
...
...
@@ -184,25 +204,46 @@
outline-width
:
1px
;
}
.monaco-editor
.review-widget
.body
.comment-form
.monaco-editor
{
display
:
none
;
.monaco-editor
.review-widget
.body
.comment-form
.monaco-editor
,
.monaco-editor
.review-widget
.body
.edit-container
.monaco-editor
{
width
:
100%
;
min-height
:
90px
;
max-height
:
500px
;
border-radius
:
3px
;
border
:
0px
;
box-sizing
:
content-box
;
padding
:
6px
0
6px
12px
;
}
.monaco-editor
.review-widget
.body
.comment-form
.monaco-editor
,
.monaco-editor
.review-widget
.body
.comment-form
.form-actions
{
display
:
none
;
}
.monaco-editor
.review-widget
.body
.comment-form
.form-actions
,
.monaco-editor
.review-widget
.body
.edit-container
.form-actions
{
overflow
:
auto
;
padding
:
10px
0
;
display
:
none
;
}
.monaco-editor
.review-widget
.body
.comment-form
.monaco-text-button
{
.monaco-editor
.review-widget
.body
.edit-container
.form-actions
{
display
:
flex
;
justify-content
:
flex-end
;
}
.monaco-editor
.review-widget
.body
.edit-textarea
{
height
:
90px
;
margin
:
5px
0
10px
0
;
}
.monaco-editor
.review-widget
.body
.comment-form
.monaco-text-button
,
.monaco-editor
.review-widget
.body
.edit-container
.monaco-text-button
{
width
:
auto
;
padding
:
4px
10px
;
margin-left
:
5px
;
}
.monaco-editor
.review-widget
.body
.comment-form
.monaco-text-button
{
float
:
right
;
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录