1 /*******************************************************************************
2 * Copyright (C) 2010, 2015 Benjamin Muskalla <bmuskalla@eclipsesource.com> and others.
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Benjamin Muskalla (EclipseSource) - initial implementation
11 * Tomasz Zarna (IBM) - show whitespace action, bug 371353
12 * Wayne Beaton (Eclipse Foundation) - Bug 433721
13 * Thomas Wolf (Paranor) - Hyperlink syntax coloring; bug 471355
14 *******************************************************************************/
15 package org
.eclipse
.egit
.ui
.internal
.dialogs
;
17 import java
.util
.ArrayList
;
18 import java
.util
.Arrays
;
19 import java
.util
.Collections
;
20 import java
.util
.Iterator
;
21 import java
.util
.List
;
23 import java
.util
.regex
.Pattern
;
25 import org
.eclipse
.core
.runtime
.Assert
;
26 import org
.eclipse
.core
.runtime
.IAdaptable
;
27 import org
.eclipse
.egit
.core
.internal
.Utils
;
28 import org
.eclipse
.egit
.ui
.Activator
;
29 import org
.eclipse
.egit
.ui
.UIPreferences
;
30 import org
.eclipse
.egit
.ui
.UIUtils
;
31 import org
.eclipse
.egit
.ui
.internal
.ActionUtils
;
32 import org
.eclipse
.egit
.ui
.internal
.CommonUtils
;
33 import org
.eclipse
.egit
.ui
.internal
.UIText
;
34 import org
.eclipse
.jface
.action
.Action
;
35 import org
.eclipse
.jface
.action
.IAction
;
36 import org
.eclipse
.jface
.action
.IMenuListener
;
37 import org
.eclipse
.jface
.action
.IMenuManager
;
38 import org
.eclipse
.jface
.action
.MenuManager
;
39 import org
.eclipse
.jface
.action
.Separator
;
40 import org
.eclipse
.jface
.action
.SubMenuManager
;
41 import org
.eclipse
.jface
.preference
.IPreferenceStore
;
42 import org
.eclipse
.jface
.resource
.ImageDescriptor
;
43 import org
.eclipse
.jface
.resource
.JFaceResources
;
44 import org
.eclipse
.jface
.text
.Document
;
45 import org
.eclipse
.jface
.text
.IDocument
;
46 import org
.eclipse
.jface
.text
.IPainter
;
47 import org
.eclipse
.jface
.text
.ITextListener
;
48 import org
.eclipse
.jface
.text
.ITextOperationTarget
;
49 import org
.eclipse
.jface
.text
.ITextViewer
;
50 import org
.eclipse
.jface
.text
.ITextViewerExtension2
;
51 import org
.eclipse
.jface
.text
.MarginPainter
;
52 import org
.eclipse
.jface
.text
.Position
;
53 import org
.eclipse
.jface
.text
.TextEvent
;
54 import org
.eclipse
.jface
.text
.WhitespaceCharacterPainter
;
55 import org
.eclipse
.jface
.text
.contentassist
.ICompletionProposal
;
56 import org
.eclipse
.jface
.text
.contentassist
.IContentAssistant
;
57 import org
.eclipse
.jface
.text
.presentation
.IPresentationReconciler
;
58 import org
.eclipse
.jface
.text
.presentation
.PresentationReconciler
;
59 import org
.eclipse
.jface
.text
.quickassist
.IQuickAssistInvocationContext
;
60 import org
.eclipse
.jface
.text
.quickassist
.IQuickAssistProcessor
;
61 import org
.eclipse
.jface
.text
.reconciler
.IReconciler
;
62 import org
.eclipse
.jface
.text
.rules
.DefaultDamagerRepairer
;
63 import org
.eclipse
.jface
.text
.source
.Annotation
;
64 import org
.eclipse
.jface
.text
.source
.AnnotationModel
;
65 import org
.eclipse
.jface
.text
.source
.IAnnotationAccess
;
66 import org
.eclipse
.jface
.text
.source
.IAnnotationModel
;
67 import org
.eclipse
.jface
.text
.source
.ISharedTextColors
;
68 import org
.eclipse
.jface
.text
.source
.ISourceViewer
;
69 import org
.eclipse
.jface
.text
.source
.SourceViewer
;
70 import org
.eclipse
.jface
.util
.IPropertyChangeListener
;
71 import org
.eclipse
.jface
.util
.PropertyChangeEvent
;
72 import org
.eclipse
.jface
.viewers
.ISelectionChangedListener
;
73 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
74 import org
.eclipse
.jgit
.util
.IntList
;
75 import org
.eclipse
.swt
.SWT
;
76 import org
.eclipse
.swt
.custom
.BidiSegmentEvent
;
77 import org
.eclipse
.swt
.custom
.BidiSegmentListener
;
78 import org
.eclipse
.swt
.custom
.StyledText
;
79 import org
.eclipse
.swt
.events
.DisposeEvent
;
80 import org
.eclipse
.swt
.events
.DisposeListener
;
81 import org
.eclipse
.swt
.graphics
.Color
;
82 import org
.eclipse
.swt
.graphics
.Font
;
83 import org
.eclipse
.swt
.graphics
.GC
;
84 import org
.eclipse
.swt
.graphics
.Image
;
85 import org
.eclipse
.swt
.graphics
.Point
;
86 import org
.eclipse
.swt
.graphics
.Rectangle
;
87 import org
.eclipse
.swt
.layout
.FillLayout
;
88 import org
.eclipse
.swt
.widgets
.Composite
;
89 import org
.eclipse
.swt
.widgets
.Display
;
90 import org
.eclipse
.swt
.widgets
.Layout
;
91 import org
.eclipse
.ui
.PlatformUI
;
92 import org
.eclipse
.ui
.actions
.ActionFactory
;
93 import org
.eclipse
.ui
.actions
.ActionFactory
.IWorkbenchAction
;
94 import org
.eclipse
.ui
.editors
.text
.EditorsUI
;
95 import org
.eclipse
.ui
.editors
.text
.TextSourceViewerConfiguration
;
96 import org
.eclipse
.ui
.handlers
.IHandlerService
;
97 import org
.eclipse
.ui
.texteditor
.AbstractTextEditor
;
98 import org
.eclipse
.ui
.texteditor
.AnnotationPreference
;
99 import org
.eclipse
.ui
.texteditor
.DefaultMarkerAnnotationAccess
;
100 import org
.eclipse
.ui
.texteditor
.ITextEditorActionDefinitionIds
;
101 import org
.eclipse
.ui
.texteditor
.IUpdate
;
102 import org
.eclipse
.ui
.texteditor
.MarkerAnnotationPreferences
;
103 import org
.eclipse
.ui
.texteditor
.SourceViewerDecorationSupport
;
104 import org
.eclipse
.ui
.themes
.IThemeManager
;
107 * Text field with support for spellchecking.
109 public class SpellcheckableMessageArea
extends Composite
{
111 static final int MAX_LINE_WIDTH
= 72;
113 private static final Pattern TRAILING_WHITE_SPACE_ON_LINES
= Pattern
114 .compile("\\h+$", Pattern
.MULTILINE
); //$NON-NLS-1$
116 private static final Pattern TRAILING_NEWLINES
= Pattern
.compile("\\v+$"); //$NON-NLS-1$
118 private static class TextViewerAction
extends Action
implements IUpdate
{
120 private int fOperationCode
= -1;
121 private ITextOperationTarget fOperationTarget
;
124 * Creates a new action.
128 * @param operationCode
131 public TextViewerAction(ITextOperationTarget target
,
133 fOperationCode
= operationCode
;
134 fOperationTarget
= target
;
139 * Updates the enabled state of the action.
142 public void update() {
143 // XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=206111
144 if (fOperationCode
== ITextOperationTarget
.REDO
) {
147 setEnabled(fOperationTarget
!= null
148 && fOperationTarget
.canDoOperation(fOperationCode
));
156 if (fOperationCode
!= -1 && fOperationTarget
!= null)
157 fOperationTarget
.doOperation(fOperationCode
);
161 private static abstract class TextEditorPropertyAction
extends Action
implements IPropertyChangeListener
{
163 private SourceViewer viewer
;
164 private String preferenceKey
;
165 private IPreferenceStore store
;
167 public TextEditorPropertyAction(String label
, SourceViewer viewer
, String preferenceKey
) {
168 super(label
, IAction
.AS_CHECK_BOX
);
169 this.viewer
= viewer
;
170 this.preferenceKey
= preferenceKey
;
171 this.store
= EditorsUI
.getPreferenceStore();
173 store
.addPropertyChangeListener(this);
174 synchronizeWithPreference();
178 public void propertyChange(PropertyChangeEvent event
) {
179 if (event
.getProperty().equals(getPreferenceKey()))
180 synchronizeWithPreference();
183 protected void synchronizeWithPreference() {
184 boolean checked
= false;
186 checked
= store
.getBoolean(getPreferenceKey());
187 if (checked
!= isChecked()) {
189 toggleState(checked
);
190 } else if (checked
) {
196 protected String
getPreferenceKey() {
197 return preferenceKey
;
202 toggleState(isChecked());
204 store
.setValue(getPreferenceKey(), isChecked());
207 public void dispose() {
209 store
.removePropertyChangeListener(this);
216 abstract protected void toggleState(boolean checked
);
218 protected ITextViewer
getTextViewer() {
222 protected IPreferenceStore
getStore() {
227 private final HyperlinkSourceViewer sourceViewer
;
229 private TextSourceViewerConfiguration configuration
;
231 private BidiSegmentListener hardWrapSegmentListener
;
233 // XXX: workaround for https://bugs.eclipse.org/400727
234 private int brokenBidiPlatformTextWidth
;
236 private IAction contentAssistAction
;
242 public SpellcheckableMessageArea(Composite parent
, String initialText
) {
243 this(parent
, initialText
, SWT
.BORDER
);
251 public SpellcheckableMessageArea(Composite parent
, String initialText
,
253 this(parent
, initialText
, false, styles
);
262 public SpellcheckableMessageArea(Composite parent
, String initialText
,
263 boolean readOnly
, int styles
) {
264 super(parent
, styles
);
265 setLayout(new FillLayout());
267 AnnotationModel annotationModel
= new AnnotationModel();
268 sourceViewer
= new HyperlinkSourceViewer(this, null,
269 SWT
.MULTI
| SWT
.V_SCROLL
| SWT
.WRAP
) {
271 protected void handleJFacePreferencesChange(
272 PropertyChangeEvent event
) {
273 if (JFaceResources
.TEXT_FONT
.equals(event
.getProperty())) {
274 Font themeFont
= UIUtils
.getFont(
275 UIPreferences
.THEME_CommitMessageEditorFont
);
276 Font jFaceFont
= JFaceResources
.getTextFont();
277 if (themeFont
.equals(jFaceFont
)) {
281 super.handleJFacePreferencesChange(event
);
285 getTextWidget().setAlwaysShowScrollBars(false);
286 getTextWidget().setFont(UIUtils
287 .getFont(UIPreferences
.THEME_CommitMessageEditorFont
));
288 sourceViewer
.setDocument(new Document());
290 int textWidth
= getCharWidth() * MAX_LINE_WIDTH
+ endSpacing
;
291 int textHeight
= getLineHeight() * 7;
292 Point size
= getTextWidget().computeSize(textWidth
, textHeight
);
293 getTextWidget().setSize(size
);
295 computeBrokenBidiPlatformTextWidth(size
.x
);
297 getTextWidget().setEditable(!readOnly
);
299 createMarginPainter();
303 final IPropertyChangeListener propertyChangeListener
= event
-> {
304 if (UIPreferences
.COMMIT_DIALOG_HARD_WRAP_MESSAGE
305 .equals(event
.getProperty())) {
306 getDisplay().asyncExec(() -> {
309 if (brokenBidiPlatformTextWidth
!= -1) {
316 Activator
.getDefault().getPreferenceStore().addPropertyChangeListener(propertyChangeListener
);
317 final IPropertyChangeListener themeListener
= event
-> {
318 String property
= event
.getProperty();
319 if (IThemeManager
.CHANGE_CURRENT_THEME
.equals(property
)
320 || UIPreferences
.THEME_CommitMessageEditorFont
322 Font themeFont
= UIUtils
323 .getFont(UIPreferences
.THEME_CommitMessageEditorFont
);
324 getDisplay().asyncExec(() -> {
326 sourceViewer
.setFont(themeFont
);
331 PlatformUI
.getWorkbench().getThemeManager()
332 .addPropertyChangeListener(themeListener
);
334 final SourceViewerDecorationSupport support
= configureAnnotationPreferences();
336 Document document
= new Document(initialText
);
338 configuration
= new HyperlinkSourceViewer
.Configuration(
339 EditorsUI
.getPreferenceStore()) {
342 public int getHyperlinkStateMask(ISourceViewer targetViewer
) {
343 if (!targetViewer
.isEditable()) {
346 return super.getHyperlinkStateMask(targetViewer
);
350 protected Map
getHyperlinkDetectorTargets(ISourceViewer targetViewer
) {
351 return getHyperlinkTargets();
355 public IReconciler
getReconciler(ISourceViewer viewer
) {
356 if (!isEditable(viewer
))
358 return super.getReconciler(sourceViewer
);
362 public IContentAssistant
getContentAssistant(ISourceViewer viewer
) {
363 if (!viewer
.isEditable())
365 IContentAssistant assistant
= createContentAssistant(viewer
);
366 // Add content assist proposal handler if assistant exists
367 if (assistant
!= null)
368 contentAssistAction
= createContentAssistAction(
374 public IPresentationReconciler
getPresentationReconciler(
375 ISourceViewer viewer
) {
376 PresentationReconciler reconciler
= new PresentationReconciler();
377 reconciler
.setDocumentPartitioning(
378 getConfiguredDocumentPartitioning(viewer
));
379 DefaultDamagerRepairer hyperlinkDamagerRepairer
= new DefaultDamagerRepairer(
380 new HyperlinkTokenScanner(this, viewer
));
381 reconciler
.setDamager(hyperlinkDamagerRepairer
,
382 IDocument
.DEFAULT_CONTENT_TYPE
);
383 reconciler
.setRepairer(hyperlinkDamagerRepairer
,
384 IDocument
.DEFAULT_CONTENT_TYPE
);
390 sourceViewer
.configure(configuration
);
391 sourceViewer
.setDocument(document
, annotationModel
);
393 configureContextMenu();
395 getTextWidget().addDisposeListener(new DisposeListener() {
397 public void widgetDisposed(DisposeEvent disposeEvent
) {
399 Activator
.getDefault().getPreferenceStore().removePropertyChangeListener(propertyChangeListener
);
400 PlatformUI
.getWorkbench().getThemeManager()
401 .removePropertyChangeListener(themeListener
);
406 private void computeBrokenBidiPlatformTextWidth(int textWidth
) {
407 class BidiSegmentListenerTester
implements BidiSegmentListener
{
411 public void lineGetSegments(BidiSegmentEvent event
) {
415 BidiSegmentListenerTester tester
= new BidiSegmentListenerTester();
416 StyledText textWidget
= getTextWidget();
417 textWidget
.addBidiSegmentListener(tester
);
418 textWidget
.setText(" "); //$NON-NLS-1$
419 textWidget
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
);
420 textWidget
.removeBidiSegmentListener(tester
);
422 brokenBidiPlatformTextWidth
= tester
.called ?
-1 : textWidth
;
425 private boolean isEditable(ISourceViewer viewer
) {
426 return viewer
!= null && viewer
.getTextWidget().getEditable();
429 private void configureHardWrap() {
430 if (shouldHardWrap()) {
431 if (hardWrapSegmentListener
== null) {
432 final StyledText textWidget
= getTextWidget();
433 hardWrapSegmentListener
= new BidiSegmentListener() {
435 public void lineGetSegments(BidiSegmentEvent e
) {
436 if (e
.widget
== textWidget
) {
437 int footerOffset
= CommonUtils
438 .getFooterOffset(textWidget
.getText());
439 if (footerOffset
>= 0
440 && e
.lineOffset
>= footerOffset
) {
444 int[] segments
= calculateWrapOffsets(e
.lineText
, MAX_LINE_WIDTH
);
445 if (segments
!= null) {
446 char[] segmentsChars
= new char[segments
.length
];
447 Arrays
.fill(segmentsChars
, '\n');
448 e
.segments
= segments
;
449 e
.segmentsChars
= segmentsChars
;
453 textWidget
.addBidiSegmentListener(hardWrapSegmentListener
);
454 textWidget
.setText(textWidget
.getText()); // XXX: workaround for https://bugs.eclipse.org/384886
456 if (brokenBidiPlatformTextWidth
!= -1) {
457 Layout restrictedWidthLayout
= new Layout() {
459 protected Point
computeSize(Composite composite
,
460 int wHint
, int hHint
, boolean flushCache
) {
461 Point size
= SpellcheckableMessageArea
.this
463 Rectangle trim
= SpellcheckableMessageArea
.this
464 .computeTrim(0, 0, 0, 0);
465 size
.x
-= trim
.width
;
466 size
.y
-= trim
.height
;
467 if (size
.x
> brokenBidiPlatformTextWidth
)
468 size
.x
= brokenBidiPlatformTextWidth
;
473 protected void layout(Composite composite
,
474 boolean flushCache
) {
475 Point size
= computeSize(composite
, SWT
.DEFAULT
,
476 SWT
.DEFAULT
, flushCache
);
477 textWidget
.setBounds(0, 0, size
.x
, size
.y
);
480 setLayout(restrictedWidthLayout
);
484 } else if (hardWrapSegmentListener
!= null) {
485 StyledText textWidget
= getTextWidget();
486 textWidget
.removeBidiSegmentListener(hardWrapSegmentListener
);
487 textWidget
.setText(textWidget
.getText()); // XXX: workaround for https://bugs.eclipse.org/384886
488 hardWrapSegmentListener
= null;
490 if (brokenBidiPlatformTextWidth
!= -1)
491 setLayout(new FillLayout());
495 private TextViewerAction
createFromActionFactory(ActionFactory factory
,
497 IWorkbenchAction template
= factory
498 .create(PlatformUI
.getWorkbench().getActiveWorkbenchWindow());
499 TextViewerAction action
= new TextViewerAction(sourceViewer
,
501 action
.setText(template
.getText());
502 action
.setImageDescriptor(template
.getImageDescriptor());
503 action
.setDisabledImageDescriptor(
504 template
.getDisabledImageDescriptor());
505 action
.setActionDefinitionId(template
.getActionDefinitionId());
510 private void configureContextMenu() {
511 final boolean editable
= isEditable(sourceViewer
);
512 TextViewerAction cutAction
;
513 TextViewerAction undoAction
;
514 TextViewerAction redoAction
;
515 TextViewerAction pasteAction
;
516 IAction quickFixAction
;
518 cutAction
= createFromActionFactory(ActionFactory
.CUT
,
519 ITextOperationTarget
.CUT
);
520 undoAction
= createFromActionFactory(ActionFactory
.UNDO
,
521 ITextOperationTarget
.UNDO
);
522 redoAction
= createFromActionFactory(ActionFactory
.REDO
,
523 ITextOperationTarget
.REDO
);
524 pasteAction
= createFromActionFactory(ActionFactory
.PASTE
,
525 ITextOperationTarget
.PASTE
);
526 quickFixAction
= new QuickfixAction(sourceViewer
);
532 quickFixAction
= null;
534 TextViewerAction copyAction
= createFromActionFactory(
535 ActionFactory
.COPY
, ITextOperationTarget
.COPY
);
536 TextViewerAction selectAllAction
= createFromActionFactory(
537 ActionFactory
.SELECT_ALL
, ITextOperationTarget
.SELECT_ALL
);
539 final TextEditorPropertyAction showWhitespaceAction
= new TextEditorPropertyAction(
540 UIText
.SpellcheckableMessageArea_showWhitespace
,
542 AbstractTextEditor
.PREFERENCE_SHOW_WHITESPACE_CHARACTERS
) {
544 private IPainter whitespaceCharPainter
;
547 public void propertyChange(PropertyChangeEvent event
) {
548 String property
= event
.getProperty();
549 if (property
.equals(getPreferenceKey())
550 || AbstractTextEditor
.PREFERENCE_SHOW_LEADING_SPACES
552 || AbstractTextEditor
.PREFERENCE_SHOW_ENCLOSED_SPACES
554 || AbstractTextEditor
.PREFERENCE_SHOW_TRAILING_SPACES
556 || AbstractTextEditor
.PREFERENCE_SHOW_LEADING_IDEOGRAPHIC_SPACES
558 || AbstractTextEditor
.PREFERENCE_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES
560 || AbstractTextEditor
.PREFERENCE_SHOW_TRAILING_IDEOGRAPHIC_SPACES
562 || AbstractTextEditor
.PREFERENCE_SHOW_LEADING_TABS
564 || AbstractTextEditor
.PREFERENCE_SHOW_ENCLOSED_TABS
566 || AbstractTextEditor
.PREFERENCE_SHOW_TRAILING_TABS
568 || AbstractTextEditor
.PREFERENCE_SHOW_CARRIAGE_RETURN
570 || AbstractTextEditor
.PREFERENCE_SHOW_LINE_FEED
572 || AbstractTextEditor
.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE
574 synchronizeWithPreference();
579 protected void toggleState(boolean checked
) {
587 * Installs the painter on the viewer.
589 private void installPainter() {
590 Assert
.isTrue(whitespaceCharPainter
== null);
591 ITextViewer v
= getTextViewer();
592 if (v
instanceof ITextViewerExtension2
) {
593 IPreferenceStore store
= getStore();
594 whitespaceCharPainter
= new WhitespaceCharacterPainter(
596 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_LEADING_SPACES
),
597 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_ENCLOSED_SPACES
),
598 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_TRAILING_SPACES
),
599 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_LEADING_IDEOGRAPHIC_SPACES
),
600 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES
),
601 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_TRAILING_IDEOGRAPHIC_SPACES
),
602 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_LEADING_TABS
),
603 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_ENCLOSED_TABS
),
604 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_TRAILING_TABS
),
605 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_CARRIAGE_RETURN
),
606 store
.getBoolean(AbstractTextEditor
.PREFERENCE_SHOW_LINE_FEED
),
607 store
.getInt(AbstractTextEditor
.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE
));
608 ((ITextViewerExtension2
) v
).addPainter(whitespaceCharPainter
);
613 * Remove the painter from the viewer.
615 private void uninstallPainter() {
616 if (whitespaceCharPainter
== null)
618 ITextViewer v
= getTextViewer();
619 if (v
instanceof ITextViewerExtension2
)
620 ((ITextViewerExtension2
) v
)
621 .removePainter(whitespaceCharPainter
);
622 whitespaceCharPainter
.deactivate(true);
623 whitespaceCharPainter
= null;
627 MenuManager contextMenu
= new MenuManager();
628 if (cutAction
!= null) {
629 contextMenu
.add(cutAction
);
631 contextMenu
.add(copyAction
);
632 if (pasteAction
!= null) {
633 contextMenu
.add(pasteAction
);
635 contextMenu
.add(selectAllAction
);
636 if (undoAction
!= null) {
637 contextMenu
.add(undoAction
);
639 if (redoAction
!= null) {
640 contextMenu
.add(redoAction
);
642 contextMenu
.add(new Separator());
643 contextMenu
.add(showWhitespaceAction
);
644 contextMenu
.add(new Separator());
647 final SubMenuManager quickFixMenu
= new SubMenuManager(contextMenu
);
648 quickFixMenu
.setVisible(true);
649 quickFixMenu
.addMenuListener(new IMenuListener() {
651 public void menuAboutToShow(IMenuManager manager
) {
652 quickFixMenu
.removeAll();
653 addProposals(quickFixMenu
);
658 final StyledText textWidget
= getTextWidget();
659 List
<IAction
> globalActions
= new ArrayList
<>();
661 globalActions
.add(cutAction
);
662 globalActions
.add(pasteAction
);
663 globalActions
.add(undoAction
);
664 globalActions
.add(redoAction
);
665 globalActions
.add(quickFixAction
);
667 globalActions
.add(copyAction
);
668 globalActions
.add(selectAllAction
);
669 if (contentAssistAction
!= null) {
670 globalActions
.add(contentAssistAction
);
672 ActionUtils
.setGlobalActions(textWidget
, globalActions
,
673 getHandlerService());
675 textWidget
.setMenu(contextMenu
.createContextMenu(textWidget
));
677 sourceViewer
.addSelectionChangedListener(new ISelectionChangedListener() {
680 public void selectionChanged(SelectionChangedEvent event
) {
681 if (cutAction
!= null)
689 sourceViewer
.addTextListener(new ITextListener() {
691 public void textChanged(TextEvent event
) {
692 if (undoAction
!= null)
694 if (redoAction
!= null)
700 textWidget
.addDisposeListener(new DisposeListener() {
702 public void widgetDisposed(DisposeEvent disposeEvent
) {
703 showWhitespaceAction
.dispose();
708 private void addProposals(final SubMenuManager quickFixMenu
) {
709 IAnnotationModel sourceModel
= sourceViewer
.getAnnotationModel();
710 if (sourceModel
== null) {
713 Iterator annotationIterator
= sourceModel
.getAnnotationIterator();
714 while (annotationIterator
.hasNext()) {
715 Annotation annotation
= (Annotation
) annotationIterator
.next();
716 boolean isDeleted
= annotation
.isMarkedDeleted();
717 boolean isIncluded
= !isDeleted
718 && includes(sourceModel
.getPosition(annotation
),
719 getTextWidget().getCaretOffset());
720 boolean isFixable
= isIncluded
&& sourceViewer
721 .getQuickAssistAssistant().canFix(annotation
);
723 IQuickAssistProcessor processor
= sourceViewer
724 .getQuickAssistAssistant().getQuickAssistProcessor();
725 IQuickAssistInvocationContext context
= sourceViewer
726 .getQuickAssistInvocationContext();
727 ICompletionProposal
[] proposals
= processor
728 .computeQuickAssistProposals(context
);
730 for (ICompletionProposal proposal
: proposals
) {
731 quickFixMenu
.add(createQuickFixAction(proposal
));
737 private boolean includes(Position position
, int caretOffset
) {
738 return position
!= null && (position
.includes(caretOffset
)
739 || (position
.offset
+ position
.length
) == caretOffset
);
742 private IAction
createQuickFixAction(final ICompletionProposal proposal
) {
743 return new Action(proposal
.getDisplayString()) {
747 proposal
.apply(sourceViewer
.getDocument());
751 public ImageDescriptor
getImageDescriptor() {
752 Image image
= proposal
.getImage();
754 return ImageDescriptor
.createFromImage(image
);
761 * Return <code>IHandlerService</code>. The default implementation uses the
762 * workbench window's service locator. Subclasses may override to access the
763 * service by using a local service locator.
765 * @return <code>IHandlerService</code> using the workbench window's service
766 * locator. Can be <code>null</code> if the service could not be
769 protected IHandlerService
getHandlerService() {
770 return CommonUtils
.getService(PlatformUI
.getWorkbench(), IHandlerService
.class);
773 private SourceViewerDecorationSupport
configureAnnotationPreferences() {
774 ISharedTextColors textColors
= EditorsUI
.getSharedTextColors();
775 IAnnotationAccess annotationAccess
= new DefaultMarkerAnnotationAccess();
776 final SourceViewerDecorationSupport support
= new SourceViewerDecorationSupport(
777 sourceViewer
, null, annotationAccess
, textColors
);
779 List annotationPreferences
= new MarkerAnnotationPreferences()
780 .getAnnotationPreferences();
781 Iterator e
= annotationPreferences
.iterator();
783 support
.setAnnotationPreference((AnnotationPreference
) e
.next());
785 support
.install(EditorsUI
.getPreferenceStore());
790 * Create margin painter and add to source viewer
792 protected void createMarginPainter() {
793 MarginPainter marginPainter
= new MarginPainter(sourceViewer
);
794 marginPainter
.setMarginRulerColumn(MAX_LINE_WIDTH
);
795 marginPainter
.setMarginRulerColor(Display
.getDefault().getSystemColor(
797 sourceViewer
.addPainter(marginPainter
);
800 private int getCharWidth() {
801 GC gc
= new GC(getTextWidget());
802 int charWidth
= gc
.getFontMetrics().getAverageCharWidth();
807 private int getLineHeight() {
808 return getTextWidget().getLineHeight();
812 * @return if the commit message should be hard-wrapped (preference)
814 private static boolean shouldHardWrap() {
815 return Activator
.getDefault().getPreferenceStore()
816 .getBoolean(UIPreferences
.COMMIT_DIALOG_HARD_WRAP_MESSAGE
);
822 public StyledText
getTextWidget() {
823 return sourceViewer
.getTextWidget();
826 private static class QuickfixAction
extends Action
{
828 private final ITextOperationTarget textOperationTarget
;
830 public QuickfixAction(ITextOperationTarget target
) {
831 textOperationTarget
= target
;
832 setActionDefinitionId(
833 ITextEditorActionDefinitionIds
.QUICK_ASSIST
);
838 if (textOperationTarget
.canDoOperation(ISourceViewer
.QUICK_ASSIST
)) {
839 textOperationTarget
.doOperation(ISourceViewer
.QUICK_ASSIST
);
845 private IAction
createContentAssistAction(
846 final SourceViewer viewer
) {
847 Action proposalAction
= new TextViewerAction(viewer
,
848 ISourceViewer
.CONTENTASSIST_PROPOSALS
);
850 .setActionDefinitionId(ITextEditorActionDefinitionIds
.CONTENT_ASSIST_PROPOSALS
);
851 return proposalAction
;
855 * Returns the commit message, converting platform-specific line endings to
856 * '\n' and hard-wrapping lines if necessary.
858 * @return commit message, without trailing whitespace on lines and without
859 * trailing empty lines.
861 public String
getCommitMessage() {
862 String text
= getText();
863 text
= Utils
.normalizeLineEndings(text
);
864 if (shouldHardWrap()) {
865 text
= wrapCommitMessage(text
);
867 text
= TRAILING_WHITE_SPACE_ON_LINES
.matcher(text
).replaceAll(""); //$NON-NLS-1$
868 text
= TRAILING_NEWLINES
.matcher(text
).replaceFirst("\n"); //$NON-NLS-1$
873 * Wraps a commit message, leaving the footer as defined by
874 * {@link CommonUtils#getFooterOffset(String)} unwrapped.
877 * of the whole commit message, including footer, using '\n' as
879 * @return the wrapped text
881 protected static String
wrapCommitMessage(String text
) {
882 // protected in order to be easily testable
883 int footerStart
= CommonUtils
.getFooterOffset(text
);
884 if (footerStart
< 0) {
885 return hardWrap(text
);
887 // Do not wrap footer lines.
888 String footer
= text
.substring(footerStart
);
889 text
= hardWrap(text
.substring(0, footerStart
));
890 return text
+ footer
;
895 * Hard-wraps the given text.
898 * the text to wrap, must use '\n' as line delimiter
899 * @return the wrapped text
901 protected static String
hardWrap(String text
) {
902 // protected for testing
903 int[] wrapOffsets
= calculateWrapOffsets(text
, MAX_LINE_WIDTH
);
904 if (wrapOffsets
!= null) {
905 StringBuilder builder
= new StringBuilder(text
.length() + wrapOffsets
.length
);
907 for (int cur
: wrapOffsets
) {
908 builder
.append(text
.substring(prev
, cur
));
909 for (int j
= cur
; j
> prev
&& builder
.charAt(builder
.length() - 1) == ' '; j
--)
910 builder
.deleteCharAt(builder
.length() - 1);
911 builder
.append('\n');
914 builder
.append(text
.substring(prev
));
915 return builder
.toString();
921 * Get hyperlink targets
923 * @return map of targets
925 protected Map
<String
, IAdaptable
> getHyperlinkTargets() {
926 return Collections
.singletonMap(EditorsUI
.DEFAULT_TEXT_EDITOR_ID
,
931 * Create content assistant
934 * @return content assistant
936 protected IContentAssistant
createContentAssistant(ISourceViewer viewer
) {
941 * Get default target for hyperlink presenter
945 protected IAdaptable
getDefaultTarget() {
952 public String
getText() {
953 return getDocument().get();
959 public IDocument
getDocument() {
960 return sourceViewer
.getDocument();
966 public void setText(String text
) {
968 getDocument().set(text
);
973 * Set the same background color to the styledText widget as the Composite
976 public void setBackground(Color color
) {
977 super.setBackground(color
);
978 StyledText textWidget
= getTextWidget();
979 textWidget
.setBackground(color
);
986 public boolean forceFocus() {
987 return getTextWidget().setFocus();
991 * Calculates wrap offsets for the given line, so that resulting lines are
992 * no longer than <code>maxLineLength</code> if possible.
995 * the line to wrap (can contain '\n', but no other line delimiters)
996 * @param maxLineLength
997 * the maximum line length
998 * @return an array of offsets where hard-wraps should be inserted, or
999 * <code>null</code> if the line does not need to be wrapped
1001 public static int[] calculateWrapOffsets(final String line
, final int maxLineLength
) {
1002 if (line
.length() == 0)
1005 IntList wrapOffsets
= new IntList();
1008 int length
= line
.length();
1009 int nofPreviousWordChars
= 0;
1010 int nofCurrentWordChars
= 0;
1011 for (int i
= 0; i
< length
; i
++) {
1012 char ch
= line
.charAt(i
);
1014 nofPreviousWordChars
+= nofCurrentWordChars
;
1015 nofCurrentWordChars
= 0;
1016 } else if (ch
== '\n') {
1019 nofPreviousWordChars
= 0;
1020 nofCurrentWordChars
= 0;
1021 } else { // a word character
1022 if (nofCurrentWordChars
== 0) {
1023 // Break only if we had a certain number of previous
1024 // non-space characters. If we had less, break only if we
1025 // had at least one non-space character and the current line
1026 // offset is at least half the maximum width. This prevents
1027 // breaking if we have <blanks><very_long_word>, and also
1028 // for things like "[1] <very_long_word>" or mark-up lists
1029 // such as " * <very_long_word>".
1030 if (nofPreviousWordChars
> maxLineLength
/ 10
1031 || nofPreviousWordChars
> 0
1032 && (i
- lineStart
) > maxLineLength
/ 2) {
1036 nofCurrentWordChars
++;
1037 if (i
>= lineStart
+ maxLineLength
) {
1038 if (wordStart
!= lineStart
) { // don't break before a single long word
1039 wrapOffsets
.add(wordStart
);
1040 lineStart
= wordStart
;
1041 nofPreviousWordChars
= 0;
1042 nofCurrentWordChars
= 0;
1048 int size
= wrapOffsets
.size();
1052 int[] result
= new int[size
];
1053 for (int i
= 0; i
< size
; i
++) {
1054 result
[i
] = wrapOffsets
.get(i
);