analyzer_plugin: Introduce messages/types for plugin analysis status

There is no code in this CL that _uses_ the new AnalysisStatus
Notification or type, but landing it is important, so that I can test
plugins which will use the analyzer_plugin package from pub.

Change-Id: I31f835d5f7b8bda3781a6962d37f1f769b22852e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404702
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer_plugin/CHANGELOG.md b/pkg/analyzer_plugin/CHANGELOG.md
index d243c8a..2368dbe 100644
--- a/pkg/analyzer_plugin/CHANGELOG.md
+++ b/pkg/analyzer_plugin/CHANGELOG.md
@@ -2,6 +2,8 @@
 - Remove `elementName()` from `RangeFactory`. Use `fragmentName()` instead.
 - Breaking changes to `DartFileEditBuilder` and `DartEditBuilder`.
 - Breaking changes to `AnalyzerConverter`.
+- Support for a plugin to send an `AnalysisStatus` notification, featuring an
+  `isAnalyzing` `bool` field.
 
 ## 0.12.0
 - Breaking changes to `DartFileEditBuilder`: `convertFunctionFromSyncToAsync`
diff --git a/pkg/analyzer_plugin/doc/api.html b/pkg/analyzer_plugin/doc/api.html
index 6c4271d..d75922d 100644
--- a/pkg/analyzer_plugin/doc/api.html
+++ b/pkg/analyzer_plugin/doc/api.html
@@ -138,6 +138,7 @@
   
   
   
+  
 <h3>Requests</h3><dl><dt class="request"><a name="request_plugin.versionCheck">plugin.versionCheck</a></dt><dd><div class="box"><pre>request: {
   "id": String
   "method": "plugin.versionCheck"
@@ -262,6 +263,26 @@
           The stack trace associated with the generation of the error, used for
           debugging the plugin.
         </p>
+      </dd></dl></dd><dt class="notification"><a name="notification_plugin.status">plugin.status</a></dt><dd><div class="box"><pre>notification: {
+  "event": "plugin.status"
+  "params": {
+    "<b>analysis</b>": <span style="color:#999999">optional</span> <a href="#type_AnalysisStatus">AnalysisStatus</a>
+  }
+}</pre></div>
+    <p>
+      Reports the current status of the plugin. Parameters are omitted if there
+      has been no change in the status represented by that parameter.
+    </p>
+    <p>
+      Only used for "new" analyzer plugins. Legacy plugins should not use
+      this type.
+    </p>
+    
+  <h4>parameters:</h4><dl><dt class="field"><b>analysis: <a href="#type_AnalysisStatus">AnalysisStatus</a><span style="color:#999999"> (optional)</span></b></dt><dd>
+        
+        <p>
+          The current status of analysis (whether analysis is being performed).
+        </p>
       </dd></dl></dd></dl>
 <h2 class="domain"><a name="domain_analysis">analysis domain</a></h2>
   <p>
@@ -844,6 +865,7 @@
   
   
   
+  
 <dl><dt class="typeDefinition"><a name="type_AddContentOverlay">AddContentOverlay: object</a></dt><dd>
     <p>
       A directive to begin overlaying the contents of a file. The supplied
@@ -957,7 +979,19 @@
       related to a specific list of files.
     </p>
     
-  <dl><dt class="value">FOLDING</dt><dt class="value">HIGHLIGHTS</dt><dt class="value">NAVIGATION</dt><dt class="value">OCCURRENCES</dt><dt class="value">OUTLINE</dt></dl></dd><dt class="typeDefinition"><a name="type_ChangeContentOverlay">ChangeContentOverlay: object</a></dt><dd>
+  <dl><dt class="value">FOLDING</dt><dt class="value">HIGHLIGHTS</dt><dt class="value">NAVIGATION</dt><dt class="value">OCCURRENCES</dt><dt class="value">OUTLINE</dt></dl></dd><dt class="typeDefinition"><a name="type_AnalysisStatus">AnalysisStatus: object</a></dt><dd>
+    <p>
+      An indication of the current state of analysis.
+    </p>
+    <p>
+      Only used for "new" analyzer plugins. Legacy plugins should not use
+      this type.
+    </p>
+    
+  <dl><dt class="field"><b>isAnalyzing: bool</b></dt><dd>
+        
+        <p>True if analysis is currently being performed.</p>
+      </dd></dl></dd><dt class="typeDefinition"><a name="type_ChangeContentOverlay">ChangeContentOverlay: object</a></dt><dd>
     <p>
       A directive to modify an existing file content overlay. One or more ranges
       of text are deleted from the old file content overlay and replaced with
@@ -2379,6 +2413,6 @@
         </p>
       </dd></dl></dd></dl>
 <h2 class="domain"><a name="index">Index</a></h2>
-<h3>Domains</h3><h4>plugin (<a href="#domain_plugin">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_plugin.versionCheck">versionCheck</a></li><li><a href="#request_plugin.shutdown">shutdown</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_plugin.error">error</a></li></ul></div></div><h4>analysis (<a href="#domain_analysis">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_analysis.getNavigation">getNavigation</a></li><li><a href="#request_analysis.handleWatchEvents">handleWatchEvents</a></li><li><a href="#request_analysis.setContextRoots">setContextRoots</a></li><li><a href="#request_analysis.setPriorityFiles">setPriorityFiles</a></li><li><a href="#request_analysis.setSubscriptions">setSubscriptions</a></li><li><a href="#request_analysis.updateContent">updateContent</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_analysis.errors">errors</a></li><li><a href="#notification_analysis.folding">folding</a></li><li><a href="#notification_analysis.highlights">highlights</a></li><li><a href="#notification_analysis.navigation">navigation</a></li><li><a href="#notification_analysis.occurrences">occurrences</a></li><li><a href="#notification_analysis.outline">outline</a></li></ul></div></div><h4>completion (<a href="#domain_completion">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_completion.getSuggestions">getSuggestions</a></li></ul></div><h4>edit (<a href="#domain_edit">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_edit.getAssists">getAssists</a></li><li><a href="#request_edit.getFixes">getFixes</a></li></ul></div><h3>Types (<a href="#types">↑</a>)</h3><div class="subindex"><ul><li><a href="#type_AddContentOverlay">AddContentOverlay</a></li><li><a href="#type_AnalysisError">AnalysisError</a></li><li><a href="#type_AnalysisErrorFixes">AnalysisErrorFixes</a></li><li><a href="#type_AnalysisErrorSeverity">AnalysisErrorSeverity</a></li><li><a href="#type_AnalysisErrorType">AnalysisErrorType</a></li><li><a href="#type_AnalysisService">AnalysisService</a></li><li><a href="#type_ChangeContentOverlay">ChangeContentOverlay</a></li><li><a href="#type_CompletionSuggestion">CompletionSuggestion</a></li><li><a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a></li><li><a href="#type_ContextRoot">ContextRoot</a></li><li><a href="#type_DiagnosticMessage">DiagnosticMessage</a></li><li><a href="#type_Element">Element</a></li><li><a href="#type_ElementKind">ElementKind</a></li><li><a href="#type_FilePath">FilePath</a></li><li><a href="#type_FoldingKind">FoldingKind</a></li><li><a href="#type_FoldingRegion">FoldingRegion</a></li><li><a href="#type_HighlightRegion">HighlightRegion</a></li><li><a href="#type_HighlightRegionType">HighlightRegionType</a></li><li><a href="#type_LinkedEditGroup">LinkedEditGroup</a></li><li><a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a></li><li><a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a></li><li><a href="#type_Location">Location</a></li><li><a href="#type_NavigationRegion">NavigationRegion</a></li><li><a href="#type_NavigationTarget">NavigationTarget</a></li><li><a href="#type_Occurrences">Occurrences</a></li><li><a href="#type_Outline">Outline</a></li><li><a href="#type_Position">Position</a></li><li><a href="#type_PrioritizedSourceChange">PrioritizedSourceChange</a></li><li><a href="#type_RefactoringKind">RefactoringKind</a></li><li><a href="#type_RefactoringMethodParameter">RefactoringMethodParameter</a></li><li><a href="#type_RefactoringMethodParameterKind">RefactoringMethodParameterKind</a></li><li><a href="#type_RefactoringProblem">RefactoringProblem</a></li><li><a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a></li><li><a href="#type_RemoveContentOverlay">RemoveContentOverlay</a></li><li><a href="#type_RequestError">RequestError</a></li><li><a href="#type_RequestErrorCode">RequestErrorCode</a></li><li><a href="#type_SourceChange">SourceChange</a></li><li><a href="#type_SourceEdit">SourceEdit</a></li><li><a href="#type_SourceFileEdit">SourceFileEdit</a></li><li><a href="#type_WatchEvent">WatchEvent</a></li><li><a href="#type_WatchEventType">WatchEventType</a></li></ul></div><h3>Refactorings (<a href="#refactorings">↑</a>)</h3><div class="subindex"><ul><li><a href="#refactoring_CONVERT_GETTER_TO_METHOD">CONVERT_GETTER_TO_METHOD</a></li><li><a href="#refactoring_CONVERT_METHOD_TO_GETTER">CONVERT_METHOD_TO_GETTER</a></li><li><a href="#refactoring_EXTRACT_LOCAL_VARIABLE">EXTRACT_LOCAL_VARIABLE</a></li><li><a href="#refactoring_EXTRACT_METHOD">EXTRACT_METHOD</a></li><li><a href="#refactoring_INLINE_LOCAL_VARIABLE">INLINE_LOCAL_VARIABLE</a></li><li><a href="#refactoring_INLINE_METHOD">INLINE_METHOD</a></li><li><a href="#refactoring_MOVE_FILE">MOVE_FILE</a></li><li><a href="#refactoring_RENAME">RENAME</a></li></ul></div>
+<h3>Domains</h3><h4>plugin (<a href="#domain_plugin">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_plugin.versionCheck">versionCheck</a></li><li><a href="#request_plugin.shutdown">shutdown</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_plugin.error">error</a></li><li><a href="#notification_plugin.status">status</a></li></ul></div></div><h4>analysis (<a href="#domain_analysis">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_analysis.getNavigation">getNavigation</a></li><li><a href="#request_analysis.handleWatchEvents">handleWatchEvents</a></li><li><a href="#request_analysis.setContextRoots">setContextRoots</a></li><li><a href="#request_analysis.setPriorityFiles">setPriorityFiles</a></li><li><a href="#request_analysis.setSubscriptions">setSubscriptions</a></li><li><a href="#request_analysis.updateContent">updateContent</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_analysis.errors">errors</a></li><li><a href="#notification_analysis.folding">folding</a></li><li><a href="#notification_analysis.highlights">highlights</a></li><li><a href="#notification_analysis.navigation">navigation</a></li><li><a href="#notification_analysis.occurrences">occurrences</a></li><li><a href="#notification_analysis.outline">outline</a></li></ul></div></div><h4>completion (<a href="#domain_completion">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_completion.getSuggestions">getSuggestions</a></li></ul></div><h4>edit (<a href="#domain_edit">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_edit.getAssists">getAssists</a></li><li><a href="#request_edit.getFixes">getFixes</a></li></ul></div><h3>Types (<a href="#types">↑</a>)</h3><div class="subindex"><ul><li><a href="#type_AddContentOverlay">AddContentOverlay</a></li><li><a href="#type_AnalysisError">AnalysisError</a></li><li><a href="#type_AnalysisErrorFixes">AnalysisErrorFixes</a></li><li><a href="#type_AnalysisErrorSeverity">AnalysisErrorSeverity</a></li><li><a href="#type_AnalysisErrorType">AnalysisErrorType</a></li><li><a href="#type_AnalysisService">AnalysisService</a></li><li><a href="#type_AnalysisStatus">AnalysisStatus</a></li><li><a href="#type_ChangeContentOverlay">ChangeContentOverlay</a></li><li><a href="#type_CompletionSuggestion">CompletionSuggestion</a></li><li><a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a></li><li><a href="#type_ContextRoot">ContextRoot</a></li><li><a href="#type_DiagnosticMessage">DiagnosticMessage</a></li><li><a href="#type_Element">Element</a></li><li><a href="#type_ElementKind">ElementKind</a></li><li><a href="#type_FilePath">FilePath</a></li><li><a href="#type_FoldingKind">FoldingKind</a></li><li><a href="#type_FoldingRegion">FoldingRegion</a></li><li><a href="#type_HighlightRegion">HighlightRegion</a></li><li><a href="#type_HighlightRegionType">HighlightRegionType</a></li><li><a href="#type_LinkedEditGroup">LinkedEditGroup</a></li><li><a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a></li><li><a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a></li><li><a href="#type_Location">Location</a></li><li><a href="#type_NavigationRegion">NavigationRegion</a></li><li><a href="#type_NavigationTarget">NavigationTarget</a></li><li><a href="#type_Occurrences">Occurrences</a></li><li><a href="#type_Outline">Outline</a></li><li><a href="#type_Position">Position</a></li><li><a href="#type_PrioritizedSourceChange">PrioritizedSourceChange</a></li><li><a href="#type_RefactoringKind">RefactoringKind</a></li><li><a href="#type_RefactoringMethodParameter">RefactoringMethodParameter</a></li><li><a href="#type_RefactoringMethodParameterKind">RefactoringMethodParameterKind</a></li><li><a href="#type_RefactoringProblem">RefactoringProblem</a></li><li><a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a></li><li><a href="#type_RemoveContentOverlay">RemoveContentOverlay</a></li><li><a href="#type_RequestError">RequestError</a></li><li><a href="#type_RequestErrorCode">RequestErrorCode</a></li><li><a href="#type_SourceChange">SourceChange</a></li><li><a href="#type_SourceEdit">SourceEdit</a></li><li><a href="#type_SourceFileEdit">SourceFileEdit</a></li><li><a href="#type_WatchEvent">WatchEvent</a></li><li><a href="#type_WatchEventType">WatchEventType</a></li></ul></div><h3>Refactorings (<a href="#refactorings">↑</a>)</h3><div class="subindex"><ul><li><a href="#refactoring_CONVERT_GETTER_TO_METHOD">CONVERT_GETTER_TO_METHOD</a></li><li><a href="#refactoring_CONVERT_METHOD_TO_GETTER">CONVERT_METHOD_TO_GETTER</a></li><li><a href="#refactoring_EXTRACT_LOCAL_VARIABLE">EXTRACT_LOCAL_VARIABLE</a></li><li><a href="#refactoring_EXTRACT_METHOD">EXTRACT_METHOD</a></li><li><a href="#refactoring_INLINE_LOCAL_VARIABLE">INLINE_LOCAL_VARIABLE</a></li><li><a href="#refactoring_INLINE_METHOD">INLINE_METHOD</a></li><li><a href="#refactoring_MOVE_FILE">MOVE_FILE</a></li><li><a href="#refactoring_RENAME">RENAME</a></li></ul></div>
 </body>
 </html>
diff --git a/pkg/analyzer_plugin/lib/protocol/protocol_constants.dart b/pkg/analyzer_plugin/lib/protocol/protocol_constants.dart
index 51857ecf..f6ab4f8 100644
--- a/pkg/analyzer_plugin/lib/protocol/protocol_constants.dart
+++ b/pkg/analyzer_plugin/lib/protocol/protocol_constants.dart
@@ -84,6 +84,8 @@
 const String PLUGIN_NOTIFICATION_ERROR_IS_FATAL = 'isFatal';
 const String PLUGIN_NOTIFICATION_ERROR_MESSAGE = 'message';
 const String PLUGIN_NOTIFICATION_ERROR_STACK_TRACE = 'stackTrace';
+const String PLUGIN_NOTIFICATION_STATUS = 'plugin.status';
+const String PLUGIN_NOTIFICATION_STATUS_ANALYSIS = 'analysis';
 const String PLUGIN_REQUEST_SHUTDOWN = 'plugin.shutdown';
 const String PLUGIN_REQUEST_VERSION_CHECK = 'plugin.versionCheck';
 const String PLUGIN_REQUEST_VERSION_CHECK_BYTE_STORE_PATH = 'byteStorePath';
diff --git a/pkg/analyzer_plugin/lib/protocol/protocol_generated.dart b/pkg/analyzer_plugin/lib/protocol/protocol_generated.dart
index aa3e59b..4534c27 100644
--- a/pkg/analyzer_plugin/lib/protocol/protocol_generated.dart
+++ b/pkg/analyzer_plugin/lib/protocol/protocol_generated.dart
@@ -1337,6 +1337,59 @@
   int get hashCode => 218088493;
 }
 
+/// AnalysisStatus
+///
+/// {
+///   "isAnalyzing": bool
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class AnalysisStatus implements HasToJson {
+  /// True if analysis is currently being performed.
+  bool isAnalyzing;
+
+  AnalysisStatus(this.isAnalyzing);
+
+  factory AnalysisStatus.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json,
+      {ClientUriConverter? clientUriConverter}) {
+    json ??= {};
+    if (json is Map) {
+      bool isAnalyzing;
+      if (json.containsKey('isAnalyzing')) {
+        isAnalyzing = jsonDecoder.decodeBool(
+            '$jsonPath.isAnalyzing', json['isAnalyzing']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'isAnalyzing');
+      }
+      return AnalysisStatus(isAnalyzing);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'AnalysisStatus', json);
+    }
+  }
+
+  @override
+  Map<String, Object> toJson({ClientUriConverter? clientUriConverter}) {
+    var result = <String, Object>{};
+    result['isAnalyzing'] = isAnalyzing;
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson(clientUriConverter: null));
+
+  @override
+  bool operator ==(other) {
+    if (other is AnalysisStatus) {
+      return isAnalyzing == other.isAnalyzing;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => isAnalyzing.hashCode;
+}
+
 /// analysis.updateContent params
 ///
 /// {
@@ -3623,6 +3676,74 @@
   int get hashCode => 9389109;
 }
 
+/// plugin.status params
+///
+/// {
+///   "analysis": optional AnalysisStatus
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class PluginStatusParams implements HasToJson {
+  /// The current status of analysis (whether analysis is being performed).
+  AnalysisStatus? analysis;
+
+  PluginStatusParams({this.analysis});
+
+  factory PluginStatusParams.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json,
+      {ClientUriConverter? clientUriConverter}) {
+    json ??= {};
+    if (json is Map) {
+      AnalysisStatus? analysis;
+      if (json.containsKey('analysis')) {
+        analysis = AnalysisStatus.fromJson(
+            jsonDecoder, '$jsonPath.analysis', json['analysis'],
+            clientUriConverter: clientUriConverter);
+      }
+      return PluginStatusParams(analysis: analysis);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'plugin.status params', json);
+    }
+  }
+
+  factory PluginStatusParams.fromNotification(Notification notification,
+      {ClientUriConverter? clientUriConverter}) {
+    return PluginStatusParams.fromJson(
+        ResponseDecoder(null), 'params', notification.params,
+        clientUriConverter: clientUriConverter);
+  }
+
+  @override
+  Map<String, Object> toJson({ClientUriConverter? clientUriConverter}) {
+    var result = <String, Object>{};
+    var analysis = this.analysis;
+    if (analysis != null) {
+      result['analysis'] =
+          analysis.toJson(clientUriConverter: clientUriConverter);
+    }
+    return result;
+  }
+
+  Notification toNotification({ClientUriConverter? clientUriConverter}) {
+    return Notification(
+        'plugin.status', toJson(clientUriConverter: clientUriConverter));
+  }
+
+  @override
+  String toString() => json.encode(toJson(clientUriConverter: null));
+
+  @override
+  bool operator ==(other) {
+    if (other is PluginStatusParams) {
+      return analysis == other.analysis;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => analysis.hashCode;
+}
+
 /// plugin.versionCheck params
 ///
 /// {
diff --git a/pkg/analyzer_plugin/test/integration/support/integration_test_methods.dart b/pkg/analyzer_plugin/test/integration/support/integration_test_methods.dart
index a8348e8..5189e4e 100644
--- a/pkg/analyzer_plugin/test/integration/support/integration_test_methods.dart
+++ b/pkg/analyzer_plugin/test/integration/support/integration_test_methods.dart
@@ -119,6 +119,22 @@
   /// Stream controller for [onPluginError].
   late StreamController<PluginErrorParams> _onPluginError;
 
+  /// Reports the current status of the plugin. Parameters are omitted if there
+  /// has been no change in the status represented by that parameter.
+  ///
+  /// Only used for "new" analyzer plugins. Legacy plugins should not use this
+  /// type.
+  ///
+  /// Parameters
+  ///
+  /// analysis: AnalysisStatus (optional)
+  ///
+  ///   The current status of analysis (whether analysis is being performed).
+  late Stream<PluginStatusParams> onPluginStatus;
+
+  /// Stream controller for [onPluginStatus].
+  late StreamController<PluginStatusParams> _onPluginStatus;
+
   /// Return the navigation information associated with the given region of the
   /// given file. If the navigation information for the given file has not yet
   /// been computed, or the most recently computed navigation information for
@@ -661,6 +677,8 @@
   void initializeInttestMixin() {
     _onPluginError = StreamController<PluginErrorParams>(sync: true);
     onPluginError = _onPluginError.stream.asBroadcastStream();
+    _onPluginStatus = StreamController<PluginStatusParams>(sync: true);
+    onPluginStatus = _onPluginStatus.stream.asBroadcastStream();
     _onAnalysisErrors = StreamController<AnalysisErrorsParams>(sync: true);
     onAnalysisErrors = _onAnalysisErrors.stream.asBroadcastStream();
     _onAnalysisFolding = StreamController<AnalysisFoldingParams>(sync: true);
@@ -688,6 +706,11 @@
         _onPluginError
             .add(PluginErrorParams.fromJson(decoder, 'params', params));
         break;
+      case 'plugin.status':
+        outOfTestExpect(params, isPluginStatusParams);
+        _onPluginStatus
+            .add(PluginStatusParams.fromJson(decoder, 'params', params));
+        break;
       case 'analysis.errors':
         outOfTestExpect(params, isAnalysisErrorsParams);
         _onAnalysisErrors
diff --git a/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart b/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
index b6775a8..efbc056 100644
--- a/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
+++ b/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
@@ -106,6 +106,14 @@
 final Matcher isAnalysisService = MatchesEnum('AnalysisService',
     ['FOLDING', 'HIGHLIGHTS', 'NAVIGATION', 'OCCURRENCES', 'OUTLINE']);
 
+/// AnalysisStatus
+///
+/// {
+///   "isAnalyzing": bool
+/// }
+final Matcher isAnalysisStatus = LazyMatcher(
+    () => MatchesJsonObject('AnalysisStatus', {'isAnalyzing': isBool}));
+
 /// ChangeContentOverlay
 ///
 /// {
@@ -1324,6 +1332,15 @@
 /// plugin.shutdown result
 final Matcher isPluginShutdownResult = isNull;
 
+/// plugin.status params
+///
+/// {
+///   "analysis": optional AnalysisStatus
+/// }
+final Matcher isPluginStatusParams = LazyMatcher(() => MatchesJsonObject(
+    'plugin.status params', null,
+    optionalFields: {'analysis': isAnalysisStatus}));
+
 /// plugin.versionCheck params
 ///
 /// {
diff --git a/pkg/analyzer_plugin/tool/spec/plugin_spec.html b/pkg/analyzer_plugin/tool/spec/plugin_spec.html
index 9984810..f5eb7f6 100644
--- a/pkg/analyzer_plugin/tool/spec/plugin_spec.html
+++ b/pkg/analyzer_plugin/tool/spec/plugin_spec.html
@@ -144,6 +144,24 @@
       </field>
     </params>
   </notification>
+  <notification event="status">
+    <p>
+      Reports the current status of the plugin. Parameters are omitted if there
+      has been no change in the status represented by that parameter.
+    </p>
+    <p>
+      Only used for "new" analyzer plugins. Legacy plugins should not use
+      this type.
+    </p>
+    <params>
+      <field name="analysis" optional="true">
+        <ref>AnalysisStatus</ref>
+        <p>
+          The current status of analysis (whether analysis is being performed).
+        </p>
+      </field>
+    </params>
+  </notification>
 </domain>
 <domain name="analysis">
   <p>
@@ -873,6 +891,28 @@
       <value><code>OUTLINE</code></value>
     </enum>
   </type>
+  <type name="AnalysisStatus">
+    <p>
+      An indication of the current state of analysis.
+    </p>
+    <p>
+      Only used for "new" analyzer plugins. Legacy plugins should not use
+      this type.
+    </p>
+    <object>
+      <field name="isAnalyzing">
+        <ref>bool</ref>
+        <p>True if analysis is currently being performed.</p>
+      </field>
+      <!--<field name="analysisTarget" optional="true">
+        <ref>String</ref>
+        <p>
+          The name of the current target of analysis. This field is
+          omitted if analyzing is false.
+        </p>
+      </field>-->
+    </object>
+  </type>
   <type name="ContextRoot">
     <p>
       A description of an analysis context.