Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
66d7a6c2
Unverified
Commit
66d7a6c2
authored
Apr 03, 2020
by
Jonah Williams
Committed by
GitHub
Apr 03, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_tools] update to package vm_service: electric boogaloo (#53809)
parent
365528aa
Changes
15
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
474 additions
and
554 deletions
+474
-554
pubspec.yaml
packages/_flutter_web_build_script/pubspec.yaml
+3
-3
devfs.dart
packages/flutter_tools/lib/src/devfs.dart
+2
-2
fuchsia_device.dart
packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+2
-2
devices.dart
packages/flutter_tools/lib/src/ios/devices.dart
+14
-12
resident_runner.dart
packages/flutter_tools/lib/src/resident_runner.dart
+1
-1
run_hot.dart
packages/flutter_tools/lib/src/run_hot.dart
+20
-18
coverage_collector.dart
packages/flutter_tools/lib/src/test/coverage_collector.dart
+2
-2
tracing.dart
packages/flutter_tools/lib/src/tracing.dart
+3
-1
vmservice.dart
packages/flutter_tools/lib/src/vmservice.dart
+198
-177
pubspec.yaml
packages/flutter_tools/pubspec.yaml
+3
-4
devfs_test.dart
packages/flutter_tools/test/general.shard/devfs_test.dart
+2
-2
fuchsia_device_test.dart
...tools/test/general.shard/fuchsia/fuchsia_device_test.dart
+1
-1
ios_device_logger_test.dart
..._tools/test/general.shard/ios/ios_device_logger_test.dart
+38
-0
resident_runner_test.dart
...lutter_tools/test/general.shard/resident_runner_test.dart
+3
-3
vmservice_test.dart
...ages/flutter_tools/test/general.shard/vmservice_test.dart
+182
-326
No files found.
packages/_flutter_web_build_script/pubspec.yaml
View file @
66d7a6c2
...
...
@@ -14,9 +14,9 @@ dependencies:
test_api
:
0.2.15
test_core
:
0.3.3
build_runner
:
1.8.
0
build_runner
:
1.8.
1
build_test
:
1.0.0
build_runner_core
:
4.5.3
build_runner_core
:
5.0.0
dart_style
:
1.3.3
code_builder
:
3.2.1
build
:
1.2.2
...
...
@@ -95,4 +95,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc
:
true
# PUBSPEC CHECKSUM:
e267
# PUBSPEC CHECKSUM:
7c61
packages/flutter_tools/lib/src/devfs.dart
View file @
66d7a6c2
...
...
@@ -4,8 +4,8 @@
import
'dart:async'
;
import
'package:json_rpc_2/json_rpc_2.dart'
as
rpc
;
import
'package:meta/meta.dart'
;
import
'package:vm_service/vm_service.dart'
as
vmservice
;
import
'asset.dart'
;
import
'base/context.dart'
;
...
...
@@ -423,7 +423,7 @@ class DevFS {
globals
.
printTrace
(
'DevFS: Creating new filesystem on the device (
$_baseUri
)'
);
try
{
_baseUri
=
await
_operations
.
create
(
fsName
);
}
on
rpc
.
RpcException
catch
(
rpcException
)
{
}
on
vmservice
.
RPCError
catch
(
rpcException
)
{
// 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
if
(
rpcException
.
code
!=
1001
)
{
rethrow
;
...
...
packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
View file @
66d7a6c2
...
...
@@ -620,7 +620,7 @@ class FuchsiaDevice extends Device {
// loopback (::1).
final
Uri
uri
=
Uri
.
parse
(
'http://[
$_ipv6Loopback
]:
$port
'
);
final
VMService
vmService
=
await
VMService
.
connect
(
uri
);
await
vmService
.
getVM
();
await
vmService
.
getVM
Old
();
await
vmService
.
refreshViews
();
for
(
final
FlutterView
flutterView
in
vmService
.
vm
.
views
)
{
if
(
flutterView
.
uiIsolate
==
null
)
{
...
...
@@ -717,7 +717,7 @@ class FuchsiaIsolateDiscoveryProtocol {
continue
;
}
}
await
service
.
getVM
();
await
service
.
getVM
Old
();
await
service
.
refreshViews
();
for
(
final
FlutterView
flutterView
in
service
.
vm
.
views
)
{
if
(
flutterView
.
uiIsolate
==
null
)
{
...
...
packages/flutter_tools/lib/src/ios/devices.dart
View file @
66d7a6c2
...
...
@@ -7,11 +7,11 @@ import 'dart:math' as math;
import
'package:meta/meta.dart'
;
import
'package:platform/platform.dart'
;
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'package:process/process.dart'
;
import
'../application_package.dart'
;
import
'../artifacts.dart'
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
import
'../base/logger.dart'
;
...
...
@@ -514,7 +514,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
// and "Flutter". The regex tries to strike a balance between not producing
// false positives and not producing false negatives.
_anyLineRegex
=
RegExp
(
r'\w+(\([^)]*\))?\[\d+\] <[A-Za-z]+>: '
);
_loggingSubscriptions
=
<
StreamSubscription
<
ServiceEvent
>>[];
_loggingSubscriptions
=
<
StreamSubscription
<
void
>>[];
}
/// Create a new [IOSDeviceLogReader].
...
...
@@ -554,7 +554,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
RegExp
_anyLineRegex
;
StreamController
<
String
>
_linesController
;
List
<
StreamSubscription
<
ServiceEvent
>>
_loggingSubscriptions
;
List
<
StreamSubscription
<
void
>>
_loggingSubscriptions
;
@override
Stream
<
String
>
get
logLines
=>
_linesController
.
stream
;
...
...
@@ -575,18 +575,20 @@ class IOSDeviceLogReader extends DeviceLogReader {
if
(
_majorSdkVersion
<
_minimumUniversalLoggingSdkVersion
)
{
return
;
}
// The VM service will not publish logging events unless the debug stream is being listened to.
// onDebugEvent listens to this stream as a side effect.
unawaited
(
connectedVmService
.
onDebugEvent
);
_loggingSubscriptions
.
add
((
await
connectedVmService
.
onStdoutEvent
).
listen
((
ServiceEvent
event
)
{
final
String
logMessage
=
event
.
message
;
if
(
logMessage
.
isNotEmpty
)
{
_linesController
.
add
(
logMessage
);
try
{
await
connectedVmService
.
streamListen
(
'Stdout'
);
}
on
vm_service
.
RPCError
{
// Do nothing, since the tool is already subscribed.
}
_loggingSubscriptions
.
add
(
connectedVmService
.
onStdoutEvent
.
listen
((
vm_service
.
Event
event
)
{
final
String
message
=
utf8
.
decode
(
base64
.
decode
(
event
.
bytes
));
if
(
message
.
isNotEmpty
)
{
_linesController
.
add
(
message
);
}
}));
}
void
_listenToSysLog
()
{
void
_listenToSysLog
()
{
// syslog is not written on iOS 13+.
if
(
_majorSdkVersion
>=
_minimumUniversalLoggingSdkVersion
)
{
return
;
...
...
@@ -641,7 +643,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
@override
void
dispose
()
{
for
(
final
StreamSubscription
<
ServiceEvent
>
loggingSubscription
in
_loggingSubscriptions
)
{
for
(
final
StreamSubscription
<
void
>
loggingSubscription
in
_loggingSubscriptions
)
{
loggingSubscription
.
cancel
();
}
_idevicesyslogProcess
?.
kill
();
...
...
packages/flutter_tools/lib/src/resident_runner.dart
View file @
66d7a6c2
...
...
@@ -232,7 +232,7 @@ class FlutterDevice {
:
vmService
.
vm
.
views
).
toList
();
}
Future
<
void
>
getVMs
()
=>
vmService
.
getVM
();
Future
<
void
>
getVMs
()
=>
vmService
.
getVM
Old
();
Future
<
void
>
exitApps
()
async
{
if
(!
device
.
supportsFlutterExit
)
{
...
...
packages/flutter_tools/lib/src/run_hot.dart
View file @
66d7a6c2
...
...
@@ -3,10 +3,8 @@
// found in the LICENSE file.
import
'dart:async'
;
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'package:platform/platform.dart'
;
import
'package:json_rpc_2/error_code.dart'
as
rpc_error_code
;
import
'package:json_rpc_2/json_rpc_2.dart'
as
rpc
;
import
'package:meta/meta.dart'
;
import
'package:pool/pool.dart'
;
import
'base/async_guard.dart'
;
...
...
@@ -110,9 +108,10 @@ class HotRunner extends ResidentRunner {
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
final
OperationResult
result
=
await
restart
(
pause:
pause
);
if
(!
result
.
isOk
)
{
throw
rpc
.
RpcException
(
rpc_error_code
.
INTERNAL_ERROR
,
throw
vm_service
.
RPCError
(
'Unable to reload sources'
,
RPCErrorCodes
.
kInternalError
,
''
,
);
}
}
...
...
@@ -121,9 +120,10 @@ class HotRunner extends ResidentRunner {
final
OperationResult
result
=
await
restart
(
fullRestart:
true
,
pause:
pause
);
if
(!
result
.
isOk
)
{
throw
rpc
.
RpcException
(
rpc_error_code
.
INTERNAL_ERROR
,
throw
vm_service
.
RPCError
(
'Unable to restart'
,
RPCErrorCodes
.
kInternalError
,
''
,
);
}
}
...
...
@@ -564,16 +564,15 @@ class HotRunner extends ResidentRunner {
if
(
benchmarkMode
)
{
final
List
<
Future
<
void
>>
isolateNotifications
=
<
Future
<
void
>>[];
for
(
final
FlutterDevice
device
in
flutterDevices
)
{
try
{
await
device
.
vmService
.
streamListen
(
'Isolate'
);
}
on
vm_service
.
RPCError
{
// Do nothing, we're already subcribed.
}
for
(
final
FlutterView
view
in
device
.
views
)
{
isolateNotifications
.
add
(
view
.
owner
.
vm
.
vmService
.
onIsolateEvent
.
then
((
Stream
<
ServiceEvent
>
serviceEvents
)
async
{
await
for
(
final
ServiceEvent
serviceEvent
in
serviceEvents
)
{
if
(
serviceEvent
.
owner
.
name
.
contains
(
'_spawn'
)
&&
serviceEvent
.
kind
==
ServiceEvent
.
kIsolateExit
)
{
return
;
}
}
view
.
owner
.
vm
.
vmService
.
onIsolateEvent
.
firstWhere
((
vm_service
.
Event
event
)
{
return
event
.
kind
==
vm_service
.
EventKind
.
kServiceExtensionAdded
;
}),
);
}
...
...
@@ -720,9 +719,12 @@ class HotRunner extends ResidentRunner {
if
(!
result
.
isOk
)
{
restartEvent
=
'restart-failed'
;
}
}
on
rpc
.
RpcException
{
}
on
vm_service
.
SentinelException
catch
(
err
,
st
)
{
restartEvent
=
'exception'
;
return
OperationResult
(
1
,
'hot restart failed to complete:
$err
\n
$st
'
,
fatal:
true
);
}
on
vm_service
.
RPCError
catch
(
err
,
st
)
{
restartEvent
=
'exception'
;
return
OperationResult
(
1
,
'hot restart failed to complete'
,
fatal:
true
);
return
OperationResult
(
1
,
'hot restart failed to complete
:
$err
\n
$st
'
,
fatal:
true
);
}
finally
{
HotEvent
(
restartEvent
,
targetPlatform:
targetPlatform
,
...
...
@@ -764,7 +766,7 @@ class HotRunner extends ResidentRunner {
);
},
);
}
on
rpc
.
RpcException
{
}
on
vm_service
.
RPCError
{
HotEvent
(
'exception'
,
targetPlatform:
targetPlatform
,
sdkName:
sdkName
,
...
...
packages/flutter_tools/lib/src/test/coverage_collector.dart
View file @
66d7a6c2
...
...
@@ -189,7 +189,7 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool Function(String) libra
Future
<
VMService
>
Function
(
Uri
)
connector
=
_defaultConnect
,
})
async
{
final
VMService
vmService
=
await
connector
(
serviceUri
);
await
vmService
.
getVM
();
await
vmService
.
getVM
Old
();
final
Map
<
String
,
dynamic
>
result
=
await
_getAllCoverage
(
vmService
,
libraryPredicate
);
await
vmService
.
close
();
...
...
@@ -197,7 +197,7 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool Function(String) libra
}
Future
<
Map
<
String
,
dynamic
>>
_getAllCoverage
(
VMService
service
,
bool
Function
(
String
)
libraryPredicate
)
async
{
await
service
.
getVM
();
await
service
.
getVM
Old
();
final
List
<
Map
<
String
,
dynamic
>>
coverage
=
<
Map
<
String
,
dynamic
>>[];
for
(
final
Isolate
isolateRef
in
service
.
vm
.
isolates
)
{
await
isolateRef
.
load
();
...
...
packages/flutter_tools/lib/src/tracing.dart
View file @
66d7a6c2
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import
'dart:async'
;
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'base/file_system.dart'
;
import
'base/logger.dart'
;
...
...
@@ -45,7 +46,8 @@ class Tracing {
);
try
{
final
Completer
<
void
>
whenFirstFrameRendered
=
Completer
<
void
>();
(
await
vmService
.
onExtensionEvent
).
listen
((
ServiceEvent
event
)
{
await
vmService
.
streamListen
(
'Extension'
);
vmService
.
onExtensionEvent
.
listen
((
vm_service
.
Event
event
)
{
if
(
event
.
extensionKind
==
'Flutter.FirstFrame'
)
{
whenFirstFrameRendered
.
complete
();
}
...
...
packages/flutter_tools/lib/src/vmservice.dart
View file @
66d7a6c2
This diff is collapsed.
Click to expand it.
packages/flutter_tools/pubspec.yaml
View file @
66d7a6c2
...
...
@@ -19,7 +19,6 @@ dependencies:
flutter_template_images
:
1.0.0
http
:
0.12.0+4
intl
:
0.16.1
json_rpc_2
:
2.1.0
meta
:
1.1.8
multicast_dns
:
0.2.2
mustache_template
:
1.0.0+1
...
...
@@ -28,10 +27,8 @@ dependencies:
process
:
3.0.12
quiver
:
2.1.3
stack_trace
:
1.9.3
stream_channel
:
2.0.0
usage
:
3.4.1
webdriver
:
2.1.2
web_socket_channel
:
1.1.0
webkit_inspection_protocol
:
0.5.0+1
xml
:
3.6.1
yaml
:
2.2.0
...
...
@@ -88,6 +85,7 @@ dependencies:
source_maps
:
0.10.9
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span
:
1.7.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
sse
:
3.2.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel
:
2.0.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_transform
:
1.2.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
string_scanner
:
1.0.5
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
sync_http
:
0.2.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
...
...
@@ -95,6 +93,7 @@ dependencies:
typed_data
:
1.1.6
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
uuid
:
2.0.4
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
watcher
:
0.9.7+14
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel
:
1.1.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies
:
collection
:
1.14.12
...
...
@@ -112,4 +111,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc
:
true
# PUBSPEC CHECKSUM:
53c2
# PUBSPEC CHECKSUM:
a585
packages/flutter_tools/test/general.shard/devfs_test.dart
View file @
66d7a6c2
...
...
@@ -14,8 +14,8 @@ import 'package:flutter_tools/src/base/net.dart';
import
'package:flutter_tools/src/compile.dart'
;
import
'package:flutter_tools/src/devfs.dart'
;
import
'package:flutter_tools/src/vmservice.dart'
;
import
'package:json_rpc_2/json_rpc_2.dart'
as
rpc
;
import
'package:mockito/mockito.dart'
;
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'../src/common.dart'
;
import
'../src/context.dart'
;
...
...
@@ -389,7 +389,7 @@ class MockVM implements VM {
Future
<
Map
<
String
,
dynamic
>>
createDevFS
(
String
fsName
)
async
{
_service
.
messages
.
add
(
'create
$fsName
'
);
if
(
_devFSExists
)
{
throw
rpc
.
RpcException
(
kFileSystemAlreadyExists
,
'File system already exists
'
);
throw
vm_service
.
RPCError
(
'File system already exists'
,
kFileSystemAlreadyExists
,
'
'
);
}
_devFSExists
=
true
;
return
<
String
,
dynamic
>{
'uri'
:
'
$_baseUri
'
};
...
...
packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
View file @
66d7a6c2
...
...
@@ -596,7 +596,7 @@ void main() {
.
thenAnswer
((
Invocation
invocation
)
async
=>
<
int
>[
1
]);
when
(
portForwarder
.
forward
(
1
))
.
thenAnswer
((
Invocation
invocation
)
async
=>
2
);
when
(
vmService
.
getVM
())
when
(
vmService
.
getVM
Old
())
.
thenAnswer
((
Invocation
invocation
)
=>
Future
<
void
>.
value
(
null
));
when
(
vmService
.
refreshViews
())
.
thenAnswer
((
Invocation
invocation
)
=>
Future
<
void
>.
value
(
null
));
...
...
packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
View file @
66d7a6c2
...
...
@@ -2,13 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:flutter_tools/src/artifacts.dart'
;
import
'package:flutter_tools/src/base/logger.dart'
;
import
'package:flutter_tools/src/build_info.dart'
;
import
'package:flutter_tools/src/convert.dart'
;
import
'package:flutter_tools/src/device.dart'
;
import
'package:flutter_tools/src/ios/devices.dart'
;
import
'package:flutter_tools/src/ios/mac.dart'
;
import
'package:flutter_tools/src/vmservice.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:vm_service/vm_service.dart'
;
import
'../../src/common.dart'
;
import
'../../src/context.dart'
;
...
...
@@ -137,6 +142,39 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
' with a non-Flutter log message following it.'
,
]);
});
testWithoutContext
(
'IOSDeviceLogReader can listen to VM Service logs'
,
()
async
{
final
MockVmService
vmService
=
MockVmService
();
final
DeviceLogReader
logReader
=
IOSDeviceLogReader
.
test
(
useSyslog:
false
,
iMobileDevice:
IMobileDevice
(
artifacts:
artifacts
,
processManager:
processManager
,
cache:
fakeCache
,
logger:
logger
,
),
);
final
StreamController
<
Event
>
controller
=
StreamController
<
Event
>();
final
Completer
<
Success
>
stdoutCompleter
=
Completer
<
Success
>();
when
(
vmService
.
streamListen
(
'Stdout'
)).
thenAnswer
((
Invocation
invocation
)
{
return
stdoutCompleter
.
future
;
});
when
(
vmService
.
onStdoutEvent
).
thenAnswer
((
Invocation
invocation
)
{
return
controller
.
stream
;
});
logReader
.
connectedVMService
=
vmService
;
stdoutCompleter
.
complete
(
Success
());
controller
.
add
(
Event
(
kind:
'Stdout'
,
timestamp:
0
,
bytes:
base64
.
encode
(
utf8
.
encode
(
' This is a message '
)),
));
// Wait for stream listeners to fire.
await
expectLater
(
logReader
.
logLines
,
emits
(
' This is a message '
));
});
}
class
MockArtifacts
extends
Mock
implements
Artifacts
{}
class
MockVmService
extends
Mock
implements
VMService
,
VmService
{}
packages/flutter_tools/test/general.shard/resident_runner_test.dart
View file @
66d7a6c2
...
...
@@ -23,8 +23,8 @@ import 'package:flutter_tools/src/resident_runner.dart';
import
'package:flutter_tools/src/run_cold.dart'
;
import
'package:flutter_tools/src/run_hot.dart'
;
import
'package:flutter_tools/src/vmservice.dart'
;
import
'package:json_rpc_2/json_rpc_2.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'../src/common.dart'
;
import
'../src/context.dart'
;
...
...
@@ -222,7 +222,7 @@ void main() {
pathToReload:
anyNamed
(
'pathToReload'
),
invalidatedFiles:
anyNamed
(
'invalidatedFiles'
),
dillOutputPath:
anyNamed
(
'dillOutputPath'
),
)).
thenThrow
(
RpcException
(
666
,
'something bad happened
'
));
)).
thenThrow
(
vm_service
.
RPCError
(
'something bad happened'
,
666
,
'
'
));
final
OperationResult
result
=
await
residentRunner
.
restart
(
fullRestart:
false
);
expect
(
result
.
fatal
,
true
);
...
...
@@ -327,7 +327,7 @@ void main() {
pathToReload:
anyNamed
(
'pathToReload'
),
invalidatedFiles:
anyNamed
(
'invalidatedFiles'
),
dillOutputPath:
anyNamed
(
'dillOutputPath'
),
)).
thenThrow
(
RpcException
(
666
,
'something bad happened
'
));
)).
thenThrow
(
vm_service
.
RPCError
(
'something bad happened'
,
666
,
'
'
));
final
OperationResult
result
=
await
residentRunner
.
restart
(
fullRestart:
true
);
expect
(
result
.
fatal
,
true
);
...
...
packages/flutter_tools/test/general.shard/vmservice_test.dart
View file @
66d7a6c2
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment