2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.hint;
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
21 import com.intellij.openapi.actionSystem.ex.AnActionListener;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.components.ServiceManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.editor.LogicalPosition;
27 import com.intellij.openapi.editor.event.*;
28 import com.intellij.openapi.editor.ex.EditorEx;
29 import com.intellij.openapi.editor.markup.*;
30 import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
31 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
32 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.project.ProjectManager;
35 import com.intellij.openapi.project.ProjectManagerAdapter;
36 import com.intellij.openapi.ui.popup.JBPopup;
37 import com.intellij.openapi.ui.popup.JBPopupFactory;
38 import com.intellij.ui.HintHint;
39 import com.intellij.ui.HintListener;
40 import com.intellij.ui.LightweightHint;
41 import com.intellij.ui.ListenerUtil;
42 import com.intellij.ui.awt.RelativePoint;
43 import com.intellij.util.Alarm;
44 import org.jetbrains.annotations.NotNull;
48 import java.awt.event.*;
49 import java.util.ArrayList;
50 import java.util.EventObject;
51 import java.util.List;
53 public class HintManagerImpl extends HintManager implements Disposable {
54 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.hint.HintManager");
56 private final AnActionListener myAnActionListener;
57 private final MyEditorManagerListener myEditorManagerListener;
58 private final EditorMouseAdapter myEditorMouseListener;
59 private final FocusListener myEditorFocusListener;
60 private final DocumentListener myEditorDocumentListener;
61 private final VisibleAreaListener myVisibleAreaListener;
62 private final CaretListener myCaretMoveListener;
64 private LightweightHint myQuestionHint = null;
65 private QuestionAction myQuestionAction = null;
67 private final List<HintInfo> myHintsStack = new ArrayList<HintInfo>();
68 private Editor myLastEditor = null;
69 private final Alarm myHideAlarm = new Alarm();
71 private static int getPriority(QuestionAction action) {
72 return action instanceof PriorityQuestionAction ? ((PriorityQuestionAction)action).getPriority() : 0;
75 public boolean canShowQuestionAction(QuestionAction action) {
76 ApplicationManager.getApplication().assertIsDispatchThread();
77 return myQuestionAction == null || getPriority(myQuestionAction) <= getPriority(action);
80 public interface ActionToIgnore {
83 private static class HintInfo {
84 final LightweightHint hint;
86 private final boolean reviveOnEditorChange;
88 private HintInfo(LightweightHint hint, int flags, boolean reviveOnEditorChange) {
91 this.reviveOnEditorChange = reviveOnEditorChange;
95 public static HintManagerImpl getInstanceImpl() {
96 return (HintManagerImpl)ServiceManager.getService(HintManager.class);
99 public HintManagerImpl(ActionManagerEx actionManagerEx, ProjectManager projectManager) {
100 myEditorManagerListener = new MyEditorManagerListener();
102 myAnActionListener = new MyAnActionListener();
103 actionManagerEx.addAnActionListener(myAnActionListener);
105 myCaretMoveListener = new CaretListener() {
106 public void caretPositionChanged(CaretEvent e) {
107 hideHints(HIDE_BY_ANY_KEY, false, false);
111 projectManager.addProjectManagerListener(new MyProjectManagerListener());
113 myEditorMouseListener = new EditorMouseAdapter() {
114 public void mousePressed(EditorMouseEvent event) {
119 myVisibleAreaListener = new VisibleAreaListener() {
120 public void visibleAreaChanged(VisibleAreaEvent e) {
121 updateScrollableHints(e);
122 hideHints(HIDE_BY_SCROLLING, false, false);
126 myEditorFocusListener = new FocusAdapter() {
127 public void focusLost(final FocusEvent e) {
128 myHideAlarm.addRequest(
131 if (!JBPopupFactory.getInstance().isChildPopupFocused(e.getComponent())) {
140 public void focusGained(FocusEvent e) {
141 myHideAlarm.cancelAllRequests();
145 myEditorDocumentListener = new DocumentAdapter() {
146 public void documentChanged(DocumentEvent event) {
147 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
148 HintInfo[] infos = myHintsStack.toArray(new HintInfo[myHintsStack.size()]);
149 for (HintInfo info : infos) {
150 if ((info.flags & HIDE_BY_TEXT_CHANGE) != 0) {
151 if (info.hint.isVisible()) {
154 myHintsStack.remove(info);
158 if (myHintsStack.isEmpty()) {
159 updateLastEditor(null);
165 public boolean performCurrentQuestionAction() {
166 if (myQuestionAction != null && myQuestionHint != null) {
167 if (myQuestionHint.isVisible()) {
168 if (LOG.isDebugEnabled()) {
169 LOG.debug("performing an action:" + myQuestionAction);
171 if (myQuestionAction.execute()) {
172 if (myQuestionHint != null) {
173 myQuestionHint.hide();
179 myQuestionAction = null;
180 myQuestionHint = null;
187 private void updateScrollableHints(VisibleAreaEvent e) {
188 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
189 for (HintInfo info : myHintsStack) {
190 if (info.hint instanceof LightweightHint && (info.flags & UPDATE_BY_SCROLLING) != 0) {
191 updateScrollableHintPosition(e, info.hint, (info.flags & HIDE_IF_OUT_OF_EDITOR) != 0);
196 public boolean hasShownHintsThatWillHideByOtherHint() {
197 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
198 for (HintInfo hintInfo : myHintsStack) {
199 if (hintInfo.hint.isVisible() && (hintInfo.flags & HIDE_BY_OTHER_HINT) != 0) return true;
204 public void dispose() {
205 ActionManagerEx.getInstanceEx().removeAnActionListener(myAnActionListener);
208 private static void updateScrollableHintPosition(VisibleAreaEvent e, LightweightHint hint, boolean hideIfOutOfEditor) {
209 if (hint.getComponent() instanceof ScrollAwareHint) {
210 ((ScrollAwareHint)hint.getComponent()).editorScrolled();
213 Editor editor = e.getEditor();
214 if (!editor.getComponent().isShowing() || editor.isOneLineMode()) return;
215 Rectangle newRectangle = e.getOldRectangle();
216 Rectangle oldRectangle = e.getNewRectangle();
217 Rectangle bounds = hint.getBounds();
218 Point location = bounds.getLocation();
220 location = SwingUtilities.convertPoint(
221 editor.getComponent().getRootPane().getLayeredPane(),
223 editor.getContentComponent()
227 int xOffset = location.x - oldRectangle.x;
228 int yOffset = location.y - oldRectangle.y;
229 location = new Point(newRectangle.x + xOffset, newRectangle.y + yOffset);
231 Rectangle newBounds = new Rectangle(location.x, location.y, bounds.width, bounds.height);
233 final boolean valid = hideIfOutOfEditor ? oldRectangle.contains(newBounds) : oldRectangle.intersects(newBounds);
235 location = SwingUtilities.convertPoint(
236 editor.getContentComponent(),
238 editor.getComponent().getRootPane().getLayeredPane()
241 hint.updateBounds(location.x, location.y);
248 public void showEditorHint(LightweightHint hint, Editor editor, short constraint, int flags, int timeout, boolean reviveOnEditorChange) {
249 LogicalPosition pos = editor.getCaretModel().getLogicalPosition();
250 Point p = getHintPosition(hint, editor, pos, constraint);
251 showEditorHint(hint, editor, p, flags, timeout, reviveOnEditorChange, new HintHint(editor, p));
255 * @param p point in layered pane coordinate system.
256 * @param reviveOnEditorChange
258 public void showEditorHint(@NotNull final LightweightHint hint,
259 @NotNull Editor editor,
262 int timeout, boolean reviveOnEditorChange) {
264 showEditorHint(hint, editor, p, flags, timeout, reviveOnEditorChange, new HintHint(editor, p));
267 public void showEditorHint(@NotNull final LightweightHint hint,
268 @NotNull Editor editor,
272 boolean reviveOnEditorChange, HintHint hintInfo) {
273 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
274 myHideAlarm.cancelAllRequests();
276 hideHints(HIDE_BY_OTHER_HINT, false, false);
278 if (editor != myLastEditor) {
282 if (!ApplicationManager.getApplication().isUnitTestMode() && !editor.getContentComponent().isShowing()) return;
284 updateLastEditor(editor);
286 getPublisher().hintShown(editor.getProject(), hint, flags);
288 Component component = hint.getComponent();
290 doShowInGivenLocation(hint, editor, p, hintInfo);
292 ListenerUtil.addMouseListener(
295 public void mousePressed(MouseEvent e) {
296 myHideAlarm.cancelAllRequests();
300 ListenerUtil.addFocusListener(
303 public void focusGained(FocusEvent e) {
304 myHideAlarm.cancelAllRequests();
309 final HintInfo info = new HintInfo(hint, flags, reviveOnEditorChange);
310 myHintsStack.add(info);
312 Timer timer = new Timer(
314 new ActionListener() {
315 public void actionPerformed(ActionEvent event) {
320 timer.setRepeats(false);
325 public void showHint(@NotNull final JComponent component, @NotNull RelativePoint p, int flags, int timeout) {
326 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
327 myHideAlarm.cancelAllRequests();
329 hideHints(HIDE_BY_OTHER_HINT, false, false);
331 final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(component, null)
332 .setRequestFocus(false)
338 ListenerUtil.addMouseListener(
341 public void mousePressed(MouseEvent e) {
342 myHideAlarm.cancelAllRequests();
346 ListenerUtil.addFocusListener(
349 public void focusGained(FocusEvent e) {
350 myHideAlarm.cancelAllRequests();
355 final HintInfo info = new HintInfo(new LightweightHint(component){
360 myHintsStack.add(info);
362 Timer timer = new Timer(
364 new ActionListener() {
365 public void actionPerformed(ActionEvent event) {
370 timer.setRepeats(false);
375 private static void doShowInGivenLocation(final LightweightHint hint, final Editor editor, final Point p, HintHint hintInfo) {
376 if (ApplicationManager.getApplication().isUnitTestMode()) return;
377 JLayeredPane layeredPane = editor.getComponent().getRootPane().getLayeredPane();
378 Dimension size = hint.getComponent().getPreferredSize();
379 if(layeredPane.getWidth() < p.x + size.width) {
380 p.x = Math.max(0, layeredPane.getWidth() - size.width);
382 if (hint.isVisible()) {
383 hint.updateBounds(p.x, p.y);
386 hint.show(layeredPane, p.x, p.y, editor.getContentComponent(), hintInfo);
390 public static void adjustEditorHintPosition(final LightweightHint hint, final Editor editor, final Point p) {
391 doShowInGivenLocation(hint, editor, p, new HintHint(editor, p));
394 public void hideAllHints() {
395 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
396 List<HintInfo> hints = new ArrayList<HintInfo>(myHintsStack);
397 for (HintInfo info : hints) {
400 myHintsStack.clear();
401 updateLastEditor(null);
405 * @return coordinates in layered pane coordinate system.
407 public Point getHintPosition(LightweightHint hint, Editor editor, short constraint) {
408 LogicalPosition pos = editor.getCaretModel().getLogicalPosition();
409 final DataContext dataContext = ((EditorEx)editor).getDataContext();
410 final Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
412 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
413 if (dominantArea == null) {
414 for (HintInfo info : myHintsStack) {
415 if (!info.hint.isSelectingHint()) continue;
416 final Rectangle rectangle = info.hint.getBounds();
418 if (rectangle != null) {
419 return getHintPositionRelativeTo(hint, editor, constraint, rectangle, pos);
424 return getHintPositionRelativeTo(hint, editor, constraint, dominantArea, pos);
427 return getHintPosition(hint, editor, pos, constraint);
430 private static Point getHintPositionRelativeTo(final LightweightHint hint,
432 final short constraint,
433 final Rectangle lookupBounds,
434 final LogicalPosition pos) {
435 Dimension hintSize = hint.getComponent().getPreferredSize();
436 JComponent editorComponent = editor.getComponent();
437 JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
438 int layeredPaneHeight = layeredPane.getHeight();
440 switch (constraint) {
443 int y = lookupBounds.y;
447 else if (y + hintSize.height >= layeredPaneHeight) {
448 y = layeredPaneHeight - hintSize.height;
450 return new Point(lookupBounds.x - hintSize.width, y);
455 int y = lookupBounds.y;
459 else if (y + hintSize.height >= layeredPaneHeight) {
460 y = layeredPaneHeight - hintSize.height;
462 return new Point(lookupBounds.x + lookupBounds.width, y);
466 Point posAboveCaret = getHintPosition(hint, editor, pos, ABOVE);
467 return new Point(lookupBounds.x, Math.min(posAboveCaret.y, lookupBounds.y - hintSize.height));
470 Point posUnderCaret = getHintPosition(hint, editor, pos, UNDER);
471 return new Point(lookupBounds.x, Math.max(posUnderCaret.y, lookupBounds.y + lookupBounds.height));
474 LOG.assertTrue(false);
480 * @return position of hint in layered pane coordinate system
482 public static Point getHintPosition(LightweightHint hint, Editor editor, LogicalPosition pos, short constraint) {
483 return getHintPosition(hint, editor, pos, pos, constraint);
486 private static Point getHintPosition(LightweightHint hint, Editor editor, LogicalPosition pos1, LogicalPosition pos2, short constraint) {
487 Point p = _getHintPosition(hint, editor, pos1, pos2, constraint);
488 JLayeredPane layeredPane = editor.getComponent().getRootPane().getLayeredPane();
489 Dimension hintSize = hint.getComponent().getPreferredSize();
490 if (constraint == ABOVE) {
492 Point p1 = _getHintPosition(hint, editor, pos1, pos2, UNDER);
493 if (p1.y + hintSize.height <= layeredPane.getSize().height) {
498 else if (constraint == UNDER) {
499 if (p.y + hintSize.height > layeredPane.getSize().height) {
500 Point p1 = _getHintPosition(hint, editor, pos1, pos2, ABOVE);
510 private static Point _getHintPosition(LightweightHint hint, Editor editor, LogicalPosition pos1, LogicalPosition pos2, short constraint) {
511 Dimension hintSize = hint.getComponent().getPreferredSize();
512 int line1 = pos1.line;
513 int col1 = pos1.column;
514 int line2 = pos2.line;
515 int col2 = pos2.column;
518 JLayeredPane layeredPane = editor.getComponent().getRootPane().getLayeredPane();
519 JComponent internalComponent = editor.getContentComponent();
520 if (constraint == RIGHT_UNDER) {
521 Point p = editor.logicalPositionToXY(new LogicalPosition(line2, col2));
522 p.y += editor.getLineHeight();
523 location = SwingUtilities.convertPoint(internalComponent, p, layeredPane);
526 Point p = editor.logicalPositionToXY(new LogicalPosition(line1, col1));
527 if (constraint == UNDER){
528 p.y += editor.getLineHeight();
530 location = SwingUtilities.convertPoint(internalComponent, p, layeredPane);
533 if (constraint == ABOVE) {
534 location.y -= hintSize.height;
535 int diff = location.x + hintSize.width - layeredPane.getWidth();
537 location.x = Math.max (location.x - diff, 0);
541 if (constraint == LEFT || constraint == RIGHT) {
542 location.y -= hintSize.height / 2;
543 if (constraint == LEFT) {
544 location.x -= hintSize.width;
551 public void showErrorHint(@NotNull Editor editor, @NotNull String text) {
552 JLabel label = HintUtil.createErrorLabel(text);
553 LightweightHint hint = new LightweightHint(label);
554 Point p = getHintPosition(hint, editor, ABOVE);
555 showEditorHint(hint, editor, p, HIDE_BY_ANY_KEY | HIDE_BY_TEXT_CHANGE | HIDE_BY_SCROLLING, 0, false);
558 public void showInformationHint(@NotNull Editor editor, @NotNull String text) {
559 JLabel label = HintUtil.createInformationLabel(text);
560 showInformationHint(editor, label);
564 public void showInformationHint(@NotNull Editor editor, @NotNull JComponent component) {
565 LightweightHint hint = new LightweightHint(component);
566 Point p = getHintPosition(hint, editor, ABOVE);
567 showEditorHint(hint, editor, p, HIDE_BY_ANY_KEY | HIDE_BY_TEXT_CHANGE | HIDE_BY_SCROLLING, 0, false);
570 public void showErrorHint(@NotNull Editor editor, @NotNull String hintText, int offset1, int offset2, short constraint, int flags, int timeout) {
571 JLabel label = HintUtil.createErrorLabel(hintText);
572 LightweightHint hint = new LightweightHint(label);
573 final LogicalPosition pos1 = editor.offsetToLogicalPosition(offset1);
574 final LogicalPosition pos2 = editor.offsetToLogicalPosition(offset2);
575 final Point p = getHintPosition(hint, editor, pos1, pos2, constraint);
576 showEditorHint(hint, editor, p, flags, timeout, false);
580 public void showQuestionHint(@NotNull Editor editor, @NotNull String hintText, int offset1, int offset2, @NotNull QuestionAction action) {
581 JLabel label = HintUtil.createQuestionLabel(hintText);
582 LightweightHint hint = new LightweightHint(label);
583 showQuestionHint(editor, offset1, offset2, hint, action, ABOVE);
586 public void showQuestionHint(@NotNull final Editor editor,
589 @NotNull final LightweightHint hint,
590 @NotNull final QuestionAction action,
591 final short constraint) {
592 final LogicalPosition pos1 = editor.offsetToLogicalPosition(offset1);
593 final LogicalPosition pos2 = editor.offsetToLogicalPosition(offset2);
594 final Point p = getHintPosition(hint, editor, pos1, pos2, constraint);
595 showQuestionHint(editor, p, offset1, offset2, hint, action);
599 public void showQuestionHint(@NotNull final Editor editor,
600 @NotNull final Point p,
603 @NotNull final LightweightHint hint,
604 @NotNull final QuestionAction action) {
605 TextAttributes attributes = new TextAttributes();
606 attributes.setEffectColor(HintUtil.QUESTION_UNDERSCORE_COLOR);
607 attributes.setEffectType(EffectType.LINE_UNDERSCORE);
608 final RangeHighlighter highlighter = editor.getMarkupModel()
609 .addRangeHighlighter(offset1, offset2, HighlighterLayer.ERROR + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
610 if (myQuestionHint != null) {
611 myQuestionHint.hide();
612 myQuestionHint = null;
613 myQuestionAction = null;
616 hint.addHintListener(new HintListener() {
617 public void hintHidden(EventObject event) {
618 if (!editor.isDisposed()) {
619 editor.getMarkupModel().removeHighlighter(highlighter);
622 if (myQuestionHint == hint) {
623 myQuestionAction = null;
624 myQuestionHint = null;
626 hint.removeHintListener(this);
630 showEditorHint(hint, editor, p, HIDE_BY_ANY_KEY | HIDE_BY_TEXT_CHANGE | UPDATE_BY_SCROLLING | HIDE_IF_OUT_OF_EDITOR, 0, false);
631 myQuestionAction = action;
632 myQuestionHint = hint;
635 private void updateLastEditor(final Editor editor) {
636 if (myLastEditor != editor) {
637 if (myLastEditor != null) {
638 myLastEditor.removeEditorMouseListener(myEditorMouseListener);
639 myLastEditor.getContentComponent().removeFocusListener(myEditorFocusListener);
640 myLastEditor.getDocument().removeDocumentListener(myEditorDocumentListener);
641 myLastEditor.getScrollingModel().removeVisibleAreaListener(myVisibleAreaListener);
642 myLastEditor.getCaretModel().removeCaretListener(myCaretMoveListener);
645 myLastEditor = editor;
646 if (myLastEditor != null) {
647 myLastEditor.addEditorMouseListener(myEditorMouseListener);
648 myLastEditor.getContentComponent().addFocusListener(myEditorFocusListener);
649 myLastEditor.getDocument().addDocumentListener(myEditorDocumentListener);
650 myLastEditor.getScrollingModel().addVisibleAreaListener(myVisibleAreaListener);
651 myLastEditor.getCaretModel().addCaretListener(myCaretMoveListener);
656 private class MyAnActionListener implements AnActionListener {
657 public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
658 if (action instanceof ActionToIgnore) return;
660 AnAction escapeAction = ActionManagerEx.getInstanceEx().getAction(IdeActions.ACTION_EDITOR_ESCAPE);
661 if (action == escapeAction) return;
663 hideHints(HIDE_BY_ANY_KEY, false, false);
667 public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
670 public void beforeEditorTyping(char c, DataContext dataContext) {}
674 * Hides all hints when selected editor changes. Unfortunately user can change
675 * selected editor by mouse. These clicks are not AnActions so they are not
676 * fired by ActionManager.
678 private final class MyEditorManagerListener extends FileEditorManagerAdapter {
679 public void selectionChanged(FileEditorManagerEvent event) {
680 hideHints(0, false, true);
685 * We have to spy for all opened projects to register MyEditorManagerListener into
686 * all opened projects.
688 private final class MyProjectManagerListener extends ProjectManagerAdapter {
689 public void projectOpened(Project project) {
690 project.getMessageBus().connect(project).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, myEditorManagerListener);
693 public void projectClosed(Project project) {
694 // avoid leak through com.intellij.codeInsight.hint.TooltipController.myCurrentTooltip
695 TooltipController.getInstance().cancelTooltips();
697 myQuestionAction = null;
698 myQuestionHint = null;
702 boolean isEscapeHandlerEnabled() {
703 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
704 for (int i = myHintsStack.size() - 1; i >= 0; i--) {
705 final HintInfo info = myHintsStack.get(i);
706 if (!info.hint.isVisible()) {
707 myHintsStack.remove(i);
711 if ((info.flags & (HIDE_BY_ESCAPE | HIDE_BY_ANY_KEY)) != 0) {
718 public boolean hideHints(int mask, boolean onlyOne, boolean editorChanged) {
719 LOG.assertTrue(SwingUtilities.isEventDispatchThread());
721 boolean done = false;
723 for (int i = myHintsStack.size() - 1; i >= 0; i--) {
724 final HintInfo info = myHintsStack.get(i);
725 if (!info.hint.isVisible()) {
726 myHintsStack.remove(i);
730 if ((info.flags & mask) != 0 || editorChanged && !info.reviveOnEditorChange) {
732 myHintsStack.remove(info);
743 if (myHintsStack.isEmpty()) {
744 updateLastEditor(null);
749 private static class EditorHintListenerHolder {
750 private static final EditorHintListener ourEditorHintPublisher =
751 ApplicationManager.getApplication().getMessageBus().syncPublisher(EditorHintListener.TOPIC);
753 private EditorHintListenerHolder() {
757 private static EditorHintListener getPublisher() {
758 return EditorHintListenerHolder.ourEditorHintPublisher;