Eclipse Plugin: Implement Quick Fix proposals on hovering over errors

徘徊边缘 提交于 2019-12-11 06:08:55

问题


What i have: I display errors in the editor as a red underline and als in the problems view. I use markers for that, but also create annotations by this code:

@TextEditorScoped
public class ErrorHighlighter {

  private final IAnnotationModel annotationModel;

  private String content = "";


  private final Set<Annotation> annotations = Sets.newConcurrentHashSet();

  private static final String ERRORMARKERID  = "org.eclipse.rwth.syntaxerror";
  private static final String WARNINGMARKERID = "org.eclipse.rwth.syntaxwarning"; 

  @Inject
  public ErrorHighlighter(@Nullable IAnnotationModel annotationModel, IStorage storage,
      ObservableModelStates observableModelStates) {
    this.annotationModel = annotationModel;
    if (annotationModel != null) {
      observableModelStates.getModelStates().stream()
          .filter(modelState -> modelState.getStorage().equals(storage))
          .forEach(this::acceptModelState);
      observableModelStates.addStorageObserver(storage, this::acceptModelState);
    }
  }

  public void acceptModelState(ModelState modelState) {
    for (Annotation annotation : annotations) {
      annotationModel.removeAnnotation(annotation);
      annotations.remove(annotation);
    }
    IMarker[] problems = null;
    int depth = IResource.DEPTH_INFINITE;
    IFile file = Misc.getEditorInput(modelState.getStorage()).getAdapter(IFile.class);
    try { //Remove all problem Markers when rebuilding the Model
       problems = file.findMarkers(ERRORMARKERID, true, depth);
       for(IMarker m: problems){
           m.delete();
       }
       problems = file.findMarkers(WARNINGMARKERID, true, depth);
       for(IMarker m: problems){
           m.delete();
       }
    } catch (CoreException e) {
       e.printStackTrace();
    }
    try {
        content = IOUtils.toString(modelState.getStorage().getContents(), "UTF-8");
    } catch (IOException e) {
        e.printStackTrace();
    } catch (CoreException e) {
        e.printStackTrace();
    }
    displaySyntaxErrors(modelState);
    displayAdditionalErrors(modelState);
  }

  private void displaySyntaxErrors(ModelState modelState) {
    ImmutableMultimap<Interval, String> syntaxErrors = modelState.getSyntaxErrors();
    for (Interval interval: syntaxErrors.keys()) {
      for (String message : syntaxErrors.get(interval)) {
        Display.getDefault().asyncExec(() -> displayError(interval, message));
      }
    }
  }

  private void displayAdditionalErrors(ModelState modelState) {
    Multimap<Interval, String> additionalErrors = modelState.getAdditionalErrors();
    for (Interval interval: additionalErrors.keys()) {
      for (String message : additionalErrors.get(interval)) {
        Display.getDefault().asyncExec(() -> displayError(interval, message));
      }
    }
  }

  private void displayError(Interval interval, String message) {
    int startIndex = interval.a;
    int stopIndex = interval.b + 1;
    Annotation annotation = null;
//    Annotation annotation =
//        new Annotation("org.eclipse.ui.workbench.texteditor.error", false, message);
//    annotations.add(annotation);
//    annotationModel.addAnnotation(annotation, new Position(startIndex, stopIndex - startIndex));
    IMarker marker = null;
    try { //create Marker to display Syntax Errors in Problems View
        IFile file = (IFile) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getEditorInput().getAdapter(IFile.class);
        if (file != null) {
            if(message.charAt(message.length()-1) == 'W'){
                marker = file.createMarker(WARNINGMARKERID);
                marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING);
            } else {
                marker = file.createMarker(ERRORMARKERID);
                marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
            }
            marker.setAttribute(IMarker.MESSAGE, message);
            marker.setAttribute(IMarker.CHAR_START, startIndex);
            marker.setAttribute(IMarker.CHAR_END, stopIndex);
            marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
            int lineNumber = 0;
            if(!content.isEmpty() && content.length()>=stopIndex){  //Convert StartIndex to Line Number
                String[] lines = content.substring(0, stopIndex).split("\r\n|\r|\n");
                lineNumber = lines.length;
            }
            marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
        }
    } catch (CoreException e) {
        e.printStackTrace();
    }
    IMarker[] problems = null;
    int depth = IResource.DEPTH_INFINITE;
    IFile file = (IFile) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getEditorInput().getAdapter(IFile.class);
    try { //Remove all problem Markers when rebuilding the Model
       problems = file.findMarkers(ERRORMARKERID, true, depth);
       for(IMarker m: problems){
           for(IMarker n: problems){
               if(MarkerUtilities.getCharStart(m) == MarkerUtilities.getCharStart(n) && m != n && MarkerUtilities.getMessage(m).equals(MarkerUtilities.getMessage(n))){
                   m.delete();
               }
           }
       }
    } catch (CoreException e) {
       e.printStackTrace();
    }
    if(marker != null){
        Annotation a = new MarkerAnnotation(marker);
        annotations.add(a);
        annotationModel.addAnnotation(a, new Position(startIndex, stopIndex - startIndex));
    }

  }
}

In my SourceViewerConfiguration I overwrite getTextHover and getAnnotationHover with this code:

@Override
  public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) {
      return new DefaultAnnotationHover(true);
  }

  public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
      return new DefaultTextHover(sourceViewer);
  }

I also overwrite getQuickAssistAssistant with this code:

public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) {
      IQuickAssistAssistant quickAssist = new QuickAssistAssistant();
      quickAssist.setQuickAssistProcessor(new TFQuickAssistProcessor());
      quickAssist.setInformationControlCreator(getInformationControlCreator(sourceViewer));
      return quickAssist;
}

With this i can right klick on errors in the code and select QuickFix, which will result in a Box appearing, which displays my Quick Fix Proposals.

What I want: How do I make this Box appear whenever I hover above the error? Thanks in advance


回答1:


To answer my own question: I replaced the getTextHover method by this one:

public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {      
      return new AbstractAnnotationHover(true) {
          public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
              IAnnotationModel model = ((SourceViewer) textViewer).getAnnotationModel();
              @SuppressWarnings("unchecked")
              Iterator<Annotation> parent = 
                      ((IAnnotationModelExtension2)model).getAnnotationIterator(hoverRegion.getOffset(), 
                              hoverRegion.getLength(), true, true);
              Iterator<?> iter = new JavaAnnotationIterator(parent, false); 
              Annotation annotation = null;
              Position position = null;
              while (iter.hasNext()) {
                  Annotation a = (Annotation) iter.next();
                  Position p = model.getPosition(a);
                  annotation = a;
                  position = p;
              }
              return new AnnotationInfo(annotation, position, textViewer) {
                  public ICompletionProposal[] getCompletionProposals() {
                      ICompletionProposal proposal1 = null;
                      IMarkerResolution [] resolutions = null;
                      ICompletionProposal [] com = null;
                      if (annotation instanceof MarkerAnnotation) {
                          resolutions = new ErrorResolution().getResolutions(((MarkerAnnotation) annotation).getMarker());
                          if(resolutions.length != 0){
                              proposal1 = new MarkerResolutionProposal(resolutions[0], 
                                      ((MarkerAnnotation) annotation).getMarker());
                              return new ICompletionProposal[] { proposal1 };
                          }
                      }
                      return com ;
                  }
              };
          } 
    };
}

which will result in a hovering box appearing over the errors offering a quick fix. Hope this helps!




回答2:


The selected answer solved my problem, but I also needed to provide textual hover help in addition to the problem resolution proposals. Different hover implementations are needed for textual help and problem resolution proposals.

Here's my implementation of a delegating Hover control, which calls the JDT Annotation hover control if the user is hovering over a problem marker and a resolution proposal is available. Otherwise, it calls an ITextHover implementation which provides textual help if available.

This is loosely based on the JDT implementation of BestMatchHover, which is how the JDT delegates to different Hovers by checking configured hovers in turn and using the first one which returns information. I had some trouble getting the proper presentation from the selected hover, and the JDT implementation was helpful in showing how to provide the appropriate implementations of IInformationControlCreator.

/**
 * This class provides a TextHover implementation which delegates to one of two different kinds of
 * TextHovers, depending on the current hover contents.
 * 
 * If the mouse is hovering over text for which
 * a problem was detected by the parser and a resolution proposal is available, presentation of the
 * hover info will be delegated to the JDT Hover control which presents the problem description and
 * Hyperlinks for the available proposals. Otherwise an ITextHover implementation will provide textual
 * hover help..
 * 
 * The JDT Hover implementation is non-API, and could therefore break in future RCP versions.
 */

public class DelegatingTextHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 {

    private ICompletionProposal[] proposals = new ICompletionProposal[0];

    protected IRegion currentHoverRegion;

    //Provides help info as text
    private ITextHover helpHover = new ITextHover() {

        @Override
        public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
            return DelegatingTextHover.this.getHoverRegion(textViewer, offset);
        }


        @Override
        public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
            String hoverHelp = ...
            return hoverHelp;

    };


    @SuppressWarnings("restriction")
    private AbstractAnnotationHover quickFixHover = new AbstractAnnotationHover(true) {

        @Override
        public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
            return null;
        }

        @Override
        public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
            return DelegatingTextHover.this.getHoverRegion(textViewer, offset);
        }

        @Override
        public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
            currentHoverRegion = hoverRegion;
            IMarker marker = ((MarkerResolutionProposal) proposals[0]).getMarker();
            String msg;
            try {
                msg = (String) marker.getAttribute(IMarker.MESSAGE);
            } catch (CoreException e) {
                msg = "Parsing Error";
            }
            return new AnnotationInfo(new Annotation(IMarker.PROBLEM, true, msg),
                    new Position(hoverRegion.getOffset(), msg.length()),
                    textViewer) {
                @Override
                public ICompletionProposal[] getCompletionProposals() {
                    return proposals;
                }
            };
        }

        @Override
        public IInformationControlCreator getHoverControlCreator() {
            // if we're using the annotation hover, delegate to superclass. Otherwise, use the default
            if (hoveringOverProblemAnnotation(currentHoverRegion)) {
                return super.getHoverControlCreator();
            }
            return new IInformationControlCreator() {
                @Override
                public IInformationControl createInformationControl(Shell shell) {
                    return new DefaultInformationControl(shell, true);
                }
            };
        }

    };

    @SuppressWarnings({ "restriction", "deprecation" })
    @Override
    public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
        proposals = ...
        if (hoveringOverProblemAnnotation(hoverRegion)) {
            return quickFixHover.getHoverInfo2(textViewer, hoverRegion);
        }
        currentHoverRegion = hoverRegion;
        return helpHover.getHoverInfo(textViewer, hoverRegion);
    }

    @SuppressWarnings("restriction")
    @Override
    public IInformationControlCreator getHoverControlCreator() {
        if (hoveringOverProblemAnnotation(currentHoverRegion)) {
            return quickFixHover.getHoverControlCreator();
        }
        return null;
    }

    public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
        Point selection= textViewer.getSelectedRange();
        if (selection.x <= offset && offset < selection.x + selection.y)
            return new Region(selection.x, selection.y);
        return new Region(offset, 0);
    }

    @Override
    public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
        return null;
    }

    public boolean hoveringOverProblemAnnotation(IRegion hoverRegion) {
        List<Point> ranges = new ArrayList<>();
        for (ICompletionProposal proposal : proposals) {
            //MarkerResolutionProposal is an implementation of ICompletionProposal which wraps a Marker
            if (proposal instanceof MarkerResolutionProposal) {
                MarkerResolutionProposal markerProposal = (MarkerResolutionProposal) proposal;
                IMarker marker = markerProposal.getMarker();
                try {
                    Integer begin =  (Integer) marker.getAttribute(IMarker.CHAR_START);
                    Integer end = (Integer) marker.getAttribute(IMarker.CHAR_END);
                    ranges.add(new Point(begin, end));
                } catch (CoreException e) {
                    //PASS
                }
            }
        }
        boolean found = false;
        for (Point p : ranges) {
            if (hoverRegion.getOffset() >= p.x && hoverRegion.getOffset() <= p.y) {
                found = true;
            }
        }
        return proposals.length > 0 && found;
    }

}


来源:https://stackoverflow.com/questions/37587545/eclipse-plugin-implement-quick-fix-proposals-on-hovering-over-errors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!