1 /*******************************************************************************
2 * Copyright (C) 2011, 2020 Dariusz Luksza <dariusz@luksza.org> and others.
3 * Copyright (C) 2011, 2013 Robin Stocker <robin@nibor.org>
4 * Copyright (C) 2011, Bernard Leach <leachbj@bouncycastle.org>
5 * Copyright (C) 2013, Michael Keppler <michael.keppler@gmx.de>
6 * Copyright (C) 2014, IBM Corporation (Markus Keller <markus_keller@ch.ibm.com>)
7 * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>)
8 * Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com>
10 * All rights reserved. This program and the accompanying materials
11 * are made available under the terms of the Eclipse Public License 2.0
12 * which accompanies this distribution, and is available at
13 * https://www.eclipse.org/legal/epl-2.0/
15 * SPDX-License-Identifier: EPL-2.0
16 *******************************************************************************/
17 package org
.eclipse
.egit
.ui
.internal
;
19 import java
.nio
.file
.Path
;
20 import java
.text
.MessageFormat
;
21 import java
.util
.Comparator
;
22 import java
.util
.Iterator
;
23 import java
.util
.LinkedList
;
24 import java
.util
.List
;
26 import java
.util
.regex
.Matcher
;
27 import java
.util
.regex
.Pattern
;
29 import org
.eclipse
.core
.commands
.Command
;
30 import org
.eclipse
.core
.commands
.ParameterizedCommand
;
31 import org
.eclipse
.core
.commands
.common
.CommandException
;
32 import org
.eclipse
.core
.expressions
.EvaluationContext
;
33 import org
.eclipse
.core
.resources
.IResource
;
34 import org
.eclipse
.egit
.ui
.Activator
;
35 import org
.eclipse
.jface
.util
.Policy
;
36 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
37 import org
.eclipse
.jgit
.lib
.Ref
;
38 import org
.eclipse
.jgit
.util
.StringUtils
;
39 import org
.eclipse
.swt
.graphics
.GC
;
40 import org
.eclipse
.swt
.widgets
.Control
;
41 import org
.eclipse
.swt
.widgets
.Table
;
42 import org
.eclipse
.ui
.ISources
;
43 import org
.eclipse
.ui
.PlatformUI
;
44 import org
.eclipse
.ui
.commands
.ICommandService
;
45 import org
.eclipse
.ui
.handlers
.IHandlerService
;
48 * Class containing all common utils
50 public class CommonUtils
{
53 * Pattern to figure out where the footer lines in a commit are.
55 * @see org.eclipse.jgit.revwalk.RevCommit#getFooterLines()
57 private static final Pattern FOOTER_PATTERN
= Pattern
58 .compile("(?:\n(?:[A-Za-z0-9-]+:[^\n]*))+\\s*$"); //$NON-NLS-1$
61 * Minimum inset to draw table column text without shortening on Windows.
62 * The code of {@link Table} suggests 4, but that lead to shortening of
65 private static final int TABLE_INSET
= 5;
67 private CommonUtils() {
68 // non-instantiable utility class
72 * Instance of comparator that sorts strings in ascending alphabetical and
73 * numerous order (also known as natural order), case insensitive.
75 * The comparator is guaranteed to return a non-zero value if
76 * {@code string1.equals(string2)} returns {@code false}.
78 public static final Comparator
<String
> STRING_ASCENDING_COMPARATOR
= new Comparator
<>() {
80 public int compare(String o1
, String o2
) {
81 if (o1
.length() == 0 || o2
.length() == 0)
82 return o1
.length() - o2
.length();
84 LinkedList
<String
> o1Parts
= splitIntoDigitAndNonDigitParts(o1
);
85 LinkedList
<String
> o2Parts
= splitIntoDigitAndNonDigitParts(o2
);
87 Iterator
<String
> o2PartsIterator
= o2Parts
.iterator();
89 for (String o1Part
: o1Parts
) {
90 if (!o2PartsIterator
.hasNext())
93 String o2Part
= o2PartsIterator
.next();
97 if (Character
.isDigit(o1Part
.charAt(0))
98 && Character
.isDigit(o2Part
.charAt(0))) {
99 o1Part
= stripLeadingZeros(o1Part
);
100 o2Part
= stripLeadingZeros(o2Part
);
101 result
= o1Part
.length() - o2Part
.length();
103 result
= o1Part
.compareToIgnoreCase(o2Part
);
105 result
= o1Part
.compareToIgnoreCase(o2Part
);
112 if (o2PartsIterator
.hasNext())
115 // strings are equal (in the Object.equals() sense)
116 // or only differ in case and/or leading zeros
117 return o1
.compareTo(o2
);
121 private LinkedList
<String
> splitIntoDigitAndNonDigitParts(
123 LinkedList
<String
> parts
= new LinkedList
<>();
125 boolean previousWasDigit
= Character
.isDigit(input
.charAt(0));
126 for (int i
= 1; i
< input
.length(); i
++) {
127 boolean isDigit
= Character
.isDigit(input
.charAt(i
));
128 if (isDigit
!= previousWasDigit
) {
129 parts
.add(input
.substring(partStart
, i
));
131 previousWasDigit
= isDigit
;
134 parts
.add(input
.substring(partStart
));
138 private String
stripLeadingZeros(String input
) {
139 for (int i
= 0; i
< input
.length(); i
++) {
140 if (input
.charAt(i
) != '0') {
141 return input
.substring(i
);
144 return ""; //$NON-NLS-1$
149 * Instance of comparator which sorts {@link Ref} names using
150 * {@link CommonUtils#STRING_ASCENDING_COMPARATOR}.
152 public static final Comparator
<Ref
> REF_ASCENDING_COMPARATOR
= Comparator
153 .comparing(Ref
::getName
, STRING_ASCENDING_COMPARATOR
);
156 * Comparator for comparing {@link IResource} by the result of
157 * {@link IResource#getName()}.
159 public static final Comparator
<IResource
> RESOURCE_NAME_COMPARATOR
= //
160 (a
, b
) -> Policy
.getComparator().compare(a
.getName(), b
.getName());
163 * Comparator for comparing (@link Path} by the result of
164 * {@link Path#toAbsolutePath()}
166 public static final Comparator
<Path
> PATH_STRING_COMPARATOR
= Comparator
167 .comparing(p
-> p
.toAbsolutePath().toString(),
168 STRING_ASCENDING_COMPARATOR
);
171 * Programmatically run a command based on its id and given selection.
174 * id of command that should be run
177 * @return {@code true} when command was successfully executed,
178 * {@code false} otherwise
180 public static boolean runCommand(String commandId
,
181 IStructuredSelection selection
) {
182 return runCommand(commandId
, selection
, null);
186 * Programmatically run a command based on its id and given selection,
187 * optionally passing parameters to the command.
190 * id of command that should be run
194 * optional command parameters to apply, may be {@code null}
195 * @return {@code true} when command was successfully executed,
196 * {@code false} otherwise
198 public static boolean runCommand(String commandId
,
199 IStructuredSelection selection
, Map
<String
, Object
> params
) {
200 ICommandService commandService
= PlatformUI
.getWorkbench()
201 .getService(ICommandService
.class);
202 Command cmd
= commandService
.getCommand(commandId
);
203 if (!cmd
.isDefined()) {
206 IHandlerService handlerService
= PlatformUI
.getWorkbench()
207 .getService(IHandlerService
.class);
208 EvaluationContext c
= null;
209 if (selection
!= null) {
210 c
= new EvaluationContext(
211 handlerService
.createContextSnapshot(false),
213 c
.addVariable(ISources
.ACTIVE_CURRENT_SELECTION_NAME
, selection
);
214 c
.removeVariable(ISources
.ACTIVE_MENU_SELECTION_NAME
);
218 handlerService
.executeCommandInContext(
219 ParameterizedCommand
.generateCommand(cmd
, params
), null,
222 handlerService
.executeCommand(
223 ParameterizedCommand
.generateCommand(cmd
, params
),
227 } catch (CommandException e
) {
228 Activator
.logError(MessageFormat
229 .format(UIText
.CommonUtils_CommandError
, commandId
), e
);
235 * Assuming that the string {@code commitMessage} is a commit message,
236 * returns the offset in the string of the footer of the commit message, if
237 * one can be found, or -1 otherwise.
239 * A footer of a commit message is defined to be the non-empty lines
240 * following the last empty line in the commit message if they have the
241 * format "key: value" as defined by
242 * {@link org.eclipse.jgit.revwalk.RevCommit#getFooterLines()}, like
243 * Change-Id: I000... or Signed-off-by: ... Empty lines at the end of the
244 * commit message are ignored.
247 * @param commitMessage
248 * text of the commit message, assumed to use '\n' as line
250 * @return the index of the beginning of the footer, if any, or -1
253 public static int getFooterOffset(String commitMessage
) {
254 if (commitMessage
== null) {
257 Matcher matcher
= FOOTER_PATTERN
.matcher(commitMessage
);
258 if (matcher
.find()) {
259 int start
= matcher
.start();
260 // Check that the line that ends at start is empty.
263 char ch
= commitMessage
.charAt(i
--);
266 } else if (!Character
.isWhitespace(ch
)) {
270 // No \n but only whitespace: first line is empty
277 * Creates a comma separated list of all non-null resource names. The last
278 * element is separated with an ampersand.
281 * the collection of {@link IResource}s.
282 * @return A comma separated list of the resource names. The last element is
283 * separated with an ampersand.
285 public static String
getResourceNames(Iterable
<IResource
> resources
) {
286 final List
<String
> names
= new LinkedList
<>();
287 for (IResource resource
: resources
) {
288 if (resource
.getName() != null) {
289 names
.add(resource
.getName());
293 return StringUtils
.join(names
, ", ", " & "); //$NON-NLS-1$ //$NON-NLS-2$
299 * @param columnHeading
301 * @return column width for a table column of the given table to fit a git
304 public static int getCommitIdColumnWidth(Control control
,
305 String columnHeading
) {
306 GC gc
= new GC(control
.getDisplay());
308 gc
.setFont(control
.getFont());
309 return Math
.max(gc
.stringExtent("bbbbbbb").x
, //$NON-NLS-1$
310 gc
.stringExtent(columnHeading
).x
) + 2 * TABLE_INSET
;