redcar-javamateview 0.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +34 -0
- data/README +58 -0
- data/Rakefile +94 -0
- data/lib/javamateview.rb +41 -0
- data/lib/javamateview/example.rb +334 -0
- data/lib/javamateview/jar/java-mateview.jar +0 -0
- data/lib/javamateview/jcodings.jar +0 -0
- data/lib/javamateview/jdom.jar +0 -0
- data/lib/javamateview/joni.jar +0 -0
- data/spec/onig/match_spec.rb +50 -0
- data/spec/parsing/dynamic_parsing_spec.rb +172 -0
- data/spec/parsing/static_parsing_spec.rb +476 -0
- data/spec/spec_helper.rb +33 -0
- data/src/com/redcareditor/mate/Bundle.java +81 -0
- data/src/com/redcareditor/mate/DoublePattern.java +89 -0
- data/src/com/redcareditor/mate/Grammar.java +129 -0
- data/src/com/redcareditor/mate/IAnnotationAreaListener.java +7 -0
- data/src/com/redcareditor/mate/IGrammarListener.java +5 -0
- data/src/com/redcareditor/mate/IncludePattern.java +10 -0
- data/src/com/redcareditor/mate/LineNumberRulerColumn.java +922 -0
- data/src/com/redcareditor/mate/Marker.java +22 -0
- data/src/com/redcareditor/mate/MateText.java +697 -0
- data/src/com/redcareditor/mate/ParseThunk.java +71 -0
- data/src/com/redcareditor/mate/Parser.java +627 -0
- data/src/com/redcareditor/mate/ParserScheduler.java +237 -0
- data/src/com/redcareditor/mate/Pattern.java +152 -0
- data/src/com/redcareditor/mate/RangeSet.java +91 -0
- data/src/com/redcareditor/mate/Scanner.java +178 -0
- data/src/com/redcareditor/mate/Scope.java +534 -0
- data/src/com/redcareditor/mate/ScopeMatcher.java +162 -0
- data/src/com/redcareditor/mate/SharedTextColors.java +110 -0
- data/src/com/redcareditor/mate/SinglePattern.java +20 -0
- data/src/com/redcareditor/mate/WhitespaceCharacterPainter.java +395 -0
- data/src/com/redcareditor/mate/colouring/Colourer.java +16 -0
- data/src/com/redcareditor/mate/colouring/swt/MarginPaintListener.java +62 -0
- data/src/com/redcareditor/mate/colouring/swt/SwtColourer.java +501 -0
- data/src/com/redcareditor/mate/document/MateDocument.java +15 -0
- data/src/com/redcareditor/mate/document/MateTextFactory.java +9 -0
- data/src/com/redcareditor/mate/document/MateTextLocation.java +8 -0
- data/src/com/redcareditor/mate/document/MateTextLocationComparator.java +17 -0
- data/src/com/redcareditor/mate/document/MateTextRange.java +18 -0
- data/src/com/redcareditor/mate/document/swt/SwtMateDocument.java +143 -0
- data/src/com/redcareditor/mate/document/swt/SwtMateTextLocation.java +88 -0
- data/src/com/redcareditor/mate/document/swt/SwtMateTextRange.java +92 -0
- data/src/com/redcareditor/mate/document/swt/SwtScopePositionUpdater.java +90 -0
- data/src/com/redcareditor/mate/undo/MateTextUndoManager.java +11 -0
- data/src/com/redcareditor/mate/undo/swt/SwtMateTextUndoManager.java +166 -0
- data/src/com/redcareditor/onig/Match.java +212 -0
- data/src/com/redcareditor/onig/NullMatch.java +57 -0
- data/src/com/redcareditor/onig/NullRx.java +29 -0
- data/src/com/redcareditor/onig/Range.java +45 -0
- data/src/com/redcareditor/onig/Rx.java +167 -0
- data/src/com/redcareditor/plist/Dict.java +119 -0
- data/src/com/redcareditor/plist/PlistNode.java +52 -0
- data/src/com/redcareditor/plist/PlistPropertyLoader.java +44 -0
- data/src/com/redcareditor/theme/ScopeSelector.java +39 -0
- data/src/com/redcareditor/theme/Theme.java +122 -0
- data/src/com/redcareditor/theme/ThemeManager.java +41 -0
- data/src/com/redcareditor/theme/ThemeSetting.java +78 -0
- data/src/com/redcareditor/util/FileUtility.java +64 -0
- data/src/com/redcareditor/util/SingleLineFormatter.java +11 -0
- data/src/com/redcareditor/util/swt/ColourUtil.java +56 -0
- data/src/ruby/java-mateview.rb +68 -0
- data/test/com/redcareditor/mate/BundleTest.java +33 -0
- data/test/com/redcareditor/mate/EmptyRangeSetTest.java +27 -0
- data/test/com/redcareditor/mate/FilledRangeSetTest.java +82 -0
- data/test/com/redcareditor/mate/GrammarTest.java +158 -0
- data/test/com/redcareditor/mate/MateTextTest.java +35 -0
- data/test/com/redcareditor/mate/ScopeMatcherMatchingTest.java +55 -0
- data/test/com/redcareditor/mate/ScopeMatcherRankingTest.java +40 -0
- data/test/com/redcareditor/onig/RxTest.java +54 -0
- data/test/com/redcareditor/plist/DictTest.java +33 -0
- data/test/com/redcareditor/theme/RailsCastThemeTest.java +37 -0
- data/test/com/redcareditor/theme/ScopeSelectorTest.java +38 -0
- data/test/com/redcareditor/theme/ThemeManagerTest.java +29 -0
- data/test/com/redcareditor/util/swt/ColourUtilTest.java +17 -0
- metadata +142 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), *%w(.. src ruby java-mateview))
|
3
|
+
|
4
|
+
JavaMateView::Bundle.load_bundles("input/")
|
5
|
+
JavaMateView::ThemeManager.load_themes("input/")
|
6
|
+
|
7
|
+
class JavaMateView::MateText
|
8
|
+
|
9
|
+
def type(line, line_offset, char)
|
10
|
+
line_start = get_text_widget.get_offset_at_line(line)
|
11
|
+
getMateDocument.replace(line_start + line_offset, 0, char)
|
12
|
+
end
|
13
|
+
|
14
|
+
def backspace(line, line_offset)
|
15
|
+
line_start = get_text_widget.get_offset_at_line(line)
|
16
|
+
getMateDocument.replace(line_start + line_offset - 1, 1, "")
|
17
|
+
end
|
18
|
+
|
19
|
+
def clean_reparse
|
20
|
+
shell = Swt::Widgets::Shell.new($display)
|
21
|
+
mt = JavaMateView::MateText.new(shell, false)
|
22
|
+
mt.set_grammar_by_name(self.parser.grammar.name)
|
23
|
+
st = mt.get_text_widget
|
24
|
+
st.text = get_text_widget.getText
|
25
|
+
mt.parser.root.pretty(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def pretty
|
29
|
+
parser.root.pretty(0)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
JavaMateView::ParserScheduler.synchronousParsing = true
|
@@ -0,0 +1,81 @@
|
|
1
|
+
package com.redcareditor.mate;
|
2
|
+
|
3
|
+
import java.io.File;
|
4
|
+
import java.util.ArrayList;
|
5
|
+
import java.util.List;
|
6
|
+
|
7
|
+
public class Bundle {
|
8
|
+
private String name;
|
9
|
+
private List<Grammar> grammars;
|
10
|
+
private static List<Bundle> bundles;
|
11
|
+
|
12
|
+
public Bundle(String name) {
|
13
|
+
this.name = name;
|
14
|
+
grammars = new ArrayList<Grammar>();
|
15
|
+
}
|
16
|
+
|
17
|
+
public static Bundle getBundleByName(String findName) {
|
18
|
+
for (Bundle b : getBundles()) {
|
19
|
+
if (b.getName().equals(findName))
|
20
|
+
return b;
|
21
|
+
}
|
22
|
+
return null;
|
23
|
+
}
|
24
|
+
|
25
|
+
public String getName() {
|
26
|
+
return name;
|
27
|
+
}
|
28
|
+
|
29
|
+
public List<Grammar> getGrammars() {
|
30
|
+
return grammars;
|
31
|
+
}
|
32
|
+
|
33
|
+
public static List<Bundle> getBundles() {
|
34
|
+
return bundles;
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Return a list of bundle names like "Ruby.tmbundle"
|
39
|
+
*/
|
40
|
+
public static List<String> bundleDirs(String textmateDir) {
|
41
|
+
List<String> names = new ArrayList<String>();
|
42
|
+
File dir = new File(textmateDir + "/Bundles");
|
43
|
+
if (dir.exists()) {
|
44
|
+
for (String name : dir.list()) {
|
45
|
+
if (name.endsWith(".tmbundle")) {
|
46
|
+
names.add(name);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
return names;
|
51
|
+
}
|
52
|
+
|
53
|
+
public static void loadBundles(String textmateDir) {
|
54
|
+
if (getBundles() == null) {
|
55
|
+
bundles = new ArrayList<Bundle>();
|
56
|
+
}
|
57
|
+
for (String bundleDir : bundleDirs(textmateDir)) {
|
58
|
+
// System.out.printf("loading %s\n", bundleDir);
|
59
|
+
Bundle bundle = new Bundle(bundleDir.split("\\.")[0]);
|
60
|
+
getBundles().add(bundle);
|
61
|
+
File grammarDirectory = new File(textmateDir + "/Bundles/" + bundleDir + "/Syntaxes");
|
62
|
+
if (grammarDirectory.exists()) {
|
63
|
+
loadGrammar(bundle, grammarDirectory);
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
private static void loadGrammar(Bundle bundle, File syntaxDir) {
|
69
|
+
for (String grammarFileName : syntaxDir.list()) {
|
70
|
+
if (isTmBundlefile(grammarFileName)) {
|
71
|
+
String grammarFilePath = syntaxDir.getPath() + "/" + grammarFileName;
|
72
|
+
Grammar grammar = new Grammar(grammarFilePath);
|
73
|
+
bundle.getGrammars().add(grammar);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
private static boolean isTmBundlefile(String syntaxFileName) {
|
79
|
+
return (syntaxFileName.endsWith(".tmLanguage") || syntaxFileName.endsWith(".plist"));
|
80
|
+
}
|
81
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
package com.redcareditor.mate;
|
2
|
+
|
3
|
+
import java.util.ArrayList;
|
4
|
+
import java.util.List;
|
5
|
+
import java.util.Map;
|
6
|
+
|
7
|
+
import org.joni.exception.ValueException;
|
8
|
+
|
9
|
+
import com.redcareditor.onig.Rx;
|
10
|
+
import com.redcareditor.plist.Dict;
|
11
|
+
import com.redcareditor.plist.PlistNode;
|
12
|
+
|
13
|
+
public class DoublePattern extends Pattern {
|
14
|
+
public String contentName;
|
15
|
+
public Rx begin;
|
16
|
+
public Rx end;
|
17
|
+
public String endString;
|
18
|
+
public String beginString;
|
19
|
+
public Map<Integer, String> beginCaptures;
|
20
|
+
public Map<Integer, String> endCaptures;
|
21
|
+
public Map<Integer, String> bothCaptures;
|
22
|
+
public List<Pattern> patterns;
|
23
|
+
public boolean hasReplacedGrammarIncludes = false;
|
24
|
+
|
25
|
+
public DoublePattern() {}
|
26
|
+
|
27
|
+
public DoublePattern(List<Pattern> grammarPatterns, Dict dict) {
|
28
|
+
name = dict.getString("name");
|
29
|
+
// System.out.printf("new DoublePattern name: %s\n", name);
|
30
|
+
try {
|
31
|
+
setDisabled(dict);
|
32
|
+
endString = dict.getString("end");
|
33
|
+
contentName = dict.getString("contentName");
|
34
|
+
begin = Rx.createRx(dict.getString("begin"));
|
35
|
+
|
36
|
+
loadCaptures(dict);
|
37
|
+
loadPatterns(grammarPatterns, dict);
|
38
|
+
grammarPatterns.add(this);
|
39
|
+
}
|
40
|
+
catch(ValueException e) {
|
41
|
+
System.out.printf("joni.exception.ValueException: %s in %s\n", e.getMessage(), dict.getString("begin"));
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
private void loadPatterns(List<Pattern> grammarPatterns, Dict dict) {
|
46
|
+
patterns = new ArrayList<Pattern>();
|
47
|
+
if (dict.containsElement("patterns")) {
|
48
|
+
for (PlistNode<?> plistPattern : dict.getArray("patterns")) {
|
49
|
+
Pattern subPattern = Pattern.createPattern(grammarPatterns, (Dict) plistPattern);
|
50
|
+
if (subPattern != null) {
|
51
|
+
patterns.add(subPattern);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
private void loadCaptures(Dict dict) {
|
58
|
+
if (dict.containsElement("beginCaptures")) {
|
59
|
+
beginCaptures = Pattern.makeCapturesFromPlist(dict.getDictionary("beginCaptures"));
|
60
|
+
}
|
61
|
+
if (dict.containsElement("captures")) {
|
62
|
+
bothCaptures = Pattern.makeCapturesFromPlist(dict.getDictionary("captures"));
|
63
|
+
}
|
64
|
+
if (dict.containsElement("endCaptures")) {
|
65
|
+
endCaptures = Pattern.makeCapturesFromPlist(dict.getDictionary("endCaptures"));
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
public void replaceGrammarIncludes() {
|
70
|
+
if (hasReplacedGrammarIncludes)
|
71
|
+
return;
|
72
|
+
Grammar ng;
|
73
|
+
int i = 0;
|
74
|
+
while (i < patterns.size()) {
|
75
|
+
Pattern p = patterns.get(i);
|
76
|
+
if (p instanceof IncludePattern) {
|
77
|
+
if ((ng = Grammar.findByScopeName(p.name)) != null) {
|
78
|
+
ng.initForUse();
|
79
|
+
patterns.remove(i);
|
80
|
+
patterns.addAll(i, ng.patterns);
|
81
|
+
i--;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
i++;
|
85
|
+
}
|
86
|
+
hasReplacedGrammarIncludes = true;
|
87
|
+
}
|
88
|
+
|
89
|
+
}
|
@@ -0,0 +1,129 @@
|
|
1
|
+
package com.redcareditor.mate;
|
2
|
+
|
3
|
+
import java.util.ArrayList;
|
4
|
+
import java.util.HashMap;
|
5
|
+
import java.util.List;
|
6
|
+
import java.util.Map;
|
7
|
+
|
8
|
+
import com.redcareditor.onig.Rx;
|
9
|
+
import com.redcareditor.plist.Dict;
|
10
|
+
import com.redcareditor.plist.PlistNode;
|
11
|
+
import com.redcareditor.plist.PlistPropertyLoader;
|
12
|
+
|
13
|
+
public class Grammar {
|
14
|
+
public String name;
|
15
|
+
public String fileName;
|
16
|
+
public Dict plist;
|
17
|
+
private PlistPropertyLoader propertyLoader;
|
18
|
+
public String[] fileTypes;
|
19
|
+
public String keyEquivalent;
|
20
|
+
public String scopeName;
|
21
|
+
public String comment;
|
22
|
+
|
23
|
+
public List<Pattern> allPatterns;
|
24
|
+
public List<Pattern> patterns;
|
25
|
+
public List<Pattern> singlePatterns;
|
26
|
+
public Map<String, List<Pattern>> repository;
|
27
|
+
public Rx firstLineMatch;
|
28
|
+
public Rx foldingStartMarker;
|
29
|
+
public Rx foldingStopMarker;
|
30
|
+
|
31
|
+
/* these are here for lookup speed purposes */
|
32
|
+
private static Map<String, Grammar> grammarsByScopeNames = new HashMap<String, Grammar>();
|
33
|
+
|
34
|
+
public Grammar(String plistFile){
|
35
|
+
this.plist = Dict.parseFile(plistFile);
|
36
|
+
propertyLoader = new PlistPropertyLoader(plist, this);
|
37
|
+
initForReference();
|
38
|
+
}
|
39
|
+
|
40
|
+
private void initForReference() {
|
41
|
+
String[] properties = new String[] { "name", "keyEquivalent", "scopeName", "comment" };
|
42
|
+
for (String property : properties) {
|
43
|
+
propertyLoader.loadStringProperty(property);
|
44
|
+
}
|
45
|
+
grammarsByScopeNames.put(scopeName, this);
|
46
|
+
if (scopeName == null)
|
47
|
+
System.out.printf("** WARNING: syntax %s has no top level scope name.\n", name);
|
48
|
+
propertyLoader.loadRegexProperty("firstLineMatch");
|
49
|
+
if (plist.containsElement("fileTypes"))
|
50
|
+
fileTypes = plist.getStrings("fileTypes");
|
51
|
+
}
|
52
|
+
|
53
|
+
public void initForUse() {
|
54
|
+
if (loaded())
|
55
|
+
return;
|
56
|
+
|
57
|
+
initForReference();
|
58
|
+
propertyLoader.loadRegexProperty("foldingStartMarker");
|
59
|
+
propertyLoader.loadRegexProperty("foldingStopMarker");
|
60
|
+
|
61
|
+
this.allPatterns = new ArrayList<Pattern>();
|
62
|
+
loadPatterns();
|
63
|
+
loadRepository();
|
64
|
+
replaceIncludePatterns();
|
65
|
+
}
|
66
|
+
|
67
|
+
private void loadPatterns() {
|
68
|
+
this.patterns = new ArrayList<Pattern>();
|
69
|
+
Dict[] dictPatterns = plist.getDictionaries("patterns");
|
70
|
+
for (Dict p : dictPatterns) {
|
71
|
+
Pattern pattern = Pattern.createPattern(allPatterns, p);
|
72
|
+
if (pattern != null) {
|
73
|
+
pattern.grammar = this;
|
74
|
+
this.patterns.add(pattern);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
private void loadRepository() {
|
80
|
+
repository = new HashMap<String, List<Pattern>>();
|
81
|
+
Dict plistRepo = plist.getDictionary("repository");
|
82
|
+
if (plistRepo == null)
|
83
|
+
return;
|
84
|
+
Dict plistRepoEntry;
|
85
|
+
for (String key : plistRepo.keys()) {
|
86
|
+
// System.out.printf("loading repository entry: %s\n", key);
|
87
|
+
List<Pattern> repoArray = new ArrayList<Pattern>();
|
88
|
+
plistRepoEntry = plistRepo.getDictionary(key);
|
89
|
+
if (plistRepoEntry.containsElement("begin") || plistRepoEntry.containsElement("match")) {
|
90
|
+
// System.out.printf(" contains begin or match\n");
|
91
|
+
Pattern pattern = Pattern.createPattern(this.allPatterns, plistRepoEntry);
|
92
|
+
if (pattern != null) {
|
93
|
+
pattern.grammar = this;
|
94
|
+
repoArray.add(pattern);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
else if (plistRepoEntry.containsElement("patterns")) {
|
98
|
+
// System.out.printf(" contains patterns\n");
|
99
|
+
for (PlistNode<?> plistPattern : plistRepoEntry.getArray("patterns")) {
|
100
|
+
Pattern pattern = Pattern.createPattern(this.allPatterns, (Dict) plistPattern);
|
101
|
+
if (pattern != null) {
|
102
|
+
pattern.grammar = this;
|
103
|
+
repoArray.add(pattern);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
repository.put(key, repoArray);
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
private void replaceIncludePatterns() {
|
112
|
+
Pattern.replaceIncludePatterns(patterns, this);
|
113
|
+
for (Pattern p : allPatterns) {
|
114
|
+
// System.out.printf("%s replaceIncludePattern for %s\n", this.name, p.name);
|
115
|
+
if (p instanceof DoublePattern) {
|
116
|
+
Pattern.replaceIncludePatterns(((DoublePattern) p).patterns, this);
|
117
|
+
}
|
118
|
+
// System.out.printf("%s replaceIncludePattern for %s [done]\n", this.name, p.name);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
public static Grammar findByScopeName(String scope) {
|
123
|
+
return grammarsByScopeNames.get(scope);
|
124
|
+
}
|
125
|
+
|
126
|
+
private boolean loaded() {
|
127
|
+
return allPatterns != null && repository != null;
|
128
|
+
}
|
129
|
+
}
|
@@ -0,0 +1,922 @@
|
|
1
|
+
/*******************************************************************************
|
2
|
+
* Copyright (c) 2000, 2008 IBM Corporation and others.
|
3
|
+
* All rights reserved. This program and the accompanying materials
|
4
|
+
* are made available under the terms of the Eclipse Public License v1.0
|
5
|
+
* which accompanies this distribution, and is available at
|
6
|
+
* http://www.eclipse.org/legal/epl-v10.html
|
7
|
+
*
|
8
|
+
* Contributors:
|
9
|
+
* IBM Corporation - initial API and implementation
|
10
|
+
* Nikolay Botev <bono8106@hotmail.com> - [rulers] Shift clicking in line number column doesn't select range - https://bugs.eclipse.org/bugs/show_bug.cgi?id=32166
|
11
|
+
* Nikolay Botev <bono8106@hotmail.com> - [rulers] Clicking in line number ruler should not trigger annotation ruler - https://bugs.eclipse.org/bugs/show_bug.cgi?id=40889
|
12
|
+
*******************************************************************************/
|
13
|
+
package com.redcareditor.mate;
|
14
|
+
|
15
|
+
import java.util.Arrays;
|
16
|
+
|
17
|
+
import org.eclipse.swt.SWT;
|
18
|
+
import org.eclipse.swt.custom.StyledText;
|
19
|
+
import org.eclipse.swt.events.DisposeEvent;
|
20
|
+
import org.eclipse.swt.events.DisposeListener;
|
21
|
+
import org.eclipse.swt.events.MouseEvent;
|
22
|
+
import org.eclipse.swt.events.MouseListener;
|
23
|
+
import org.eclipse.swt.events.MouseMoveListener;
|
24
|
+
import org.eclipse.swt.events.PaintEvent;
|
25
|
+
import org.eclipse.swt.events.PaintListener;
|
26
|
+
import org.eclipse.swt.graphics.Color;
|
27
|
+
import org.eclipse.swt.graphics.Font;
|
28
|
+
import org.eclipse.swt.graphics.FontMetrics;
|
29
|
+
import org.eclipse.swt.graphics.GC;
|
30
|
+
import org.eclipse.swt.graphics.Image;
|
31
|
+
import org.eclipse.swt.graphics.Point;
|
32
|
+
import org.eclipse.swt.graphics.Rectangle;
|
33
|
+
import org.eclipse.swt.widgets.Canvas;
|
34
|
+
import org.eclipse.swt.widgets.Composite;
|
35
|
+
import org.eclipse.swt.widgets.Control;
|
36
|
+
import org.eclipse.swt.widgets.Display;
|
37
|
+
import org.eclipse.swt.widgets.TypedListener;
|
38
|
+
|
39
|
+
import org.eclipse.jface.text.BadLocationException;
|
40
|
+
import org.eclipse.jface.text.IDocument;
|
41
|
+
import org.eclipse.jface.text.IRegion;
|
42
|
+
import org.eclipse.jface.text.ITextListener;
|
43
|
+
import org.eclipse.jface.text.ITextViewer;
|
44
|
+
import org.eclipse.jface.text.ITextViewerExtension;
|
45
|
+
import org.eclipse.jface.text.ITextViewerExtension5;
|
46
|
+
import org.eclipse.jface.text.IViewportListener;
|
47
|
+
import org.eclipse.jface.text.JFaceTextUtil;
|
48
|
+
import org.eclipse.jface.text.TextEvent;
|
49
|
+
|
50
|
+
import org.eclipse.jface.text.source.IVerticalRulerColumn;
|
51
|
+
import org.eclipse.jface.text.source.CompositeRuler;
|
52
|
+
import org.eclipse.jface.text.source.ILineRange;
|
53
|
+
import org.eclipse.jface.text.source.IAnnotationModel;
|
54
|
+
|
55
|
+
|
56
|
+
/**
|
57
|
+
* A vertical ruler column displaying line numbers.
|
58
|
+
* Clients usually instantiate and configure object of this class.
|
59
|
+
*
|
60
|
+
* @since 2.0
|
61
|
+
*/
|
62
|
+
public class LineNumberRulerColumn implements IVerticalRulerColumn {
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Internal listener class.
|
66
|
+
*/
|
67
|
+
class InternalListener implements IViewportListener, ITextListener {
|
68
|
+
|
69
|
+
/**
|
70
|
+
* @since 3.1
|
71
|
+
*/
|
72
|
+
private boolean fCachedRedrawState= true;
|
73
|
+
|
74
|
+
/*
|
75
|
+
* @see IViewportListener#viewportChanged(int)
|
76
|
+
*/
|
77
|
+
public void viewportChanged(int verticalPosition) {
|
78
|
+
if (fCachedRedrawState && verticalPosition != fScrollPos)
|
79
|
+
redraw();
|
80
|
+
}
|
81
|
+
|
82
|
+
/*
|
83
|
+
* @see ITextListener#textChanged(TextEvent)
|
84
|
+
*/
|
85
|
+
public void textChanged(TextEvent event) {
|
86
|
+
|
87
|
+
fCachedRedrawState= event.getViewerRedrawState();
|
88
|
+
if (!fCachedRedrawState)
|
89
|
+
return;
|
90
|
+
|
91
|
+
if (updateNumberOfDigits()) {
|
92
|
+
computeIndentations();
|
93
|
+
layout(event.getViewerRedrawState());
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
|
97
|
+
boolean viewerCompletelyShown= isViewerCompletelyShown();
|
98
|
+
if (viewerCompletelyShown || fSensitiveToTextChanges || event.getDocumentEvent() == null)
|
99
|
+
postRedraw();
|
100
|
+
fSensitiveToTextChanges= viewerCompletelyShown;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Handles all the mouse interaction in this line number ruler column.
|
106
|
+
*/
|
107
|
+
class MouseHandler implements MouseListener, MouseMoveListener {
|
108
|
+
|
109
|
+
/** The cached view port size. */
|
110
|
+
private int fCachedViewportSize;
|
111
|
+
/** The area of the line at which line selection started. */
|
112
|
+
private int fStartLineOffset;
|
113
|
+
/** The number of the line at which line selection started. */
|
114
|
+
private int fStartLineNumber;
|
115
|
+
/** The auto scroll direction. */
|
116
|
+
private int fAutoScrollDirection;
|
117
|
+
/* @since 3.2 */
|
118
|
+
private boolean fIsListeningForMove= false;
|
119
|
+
|
120
|
+
/*
|
121
|
+
* @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
|
122
|
+
*/
|
123
|
+
public void mouseUp(MouseEvent event) {
|
124
|
+
// see bug 45700
|
125
|
+
if (event.button == 1) {
|
126
|
+
stopSelecting();
|
127
|
+
stopAutoScroll();
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
/*
|
132
|
+
* @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
|
133
|
+
*/
|
134
|
+
public void mouseDown(MouseEvent event) {
|
135
|
+
fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y);
|
136
|
+
// see bug 45700
|
137
|
+
if (event.button == 1) {
|
138
|
+
startSelecting((event.stateMask & SWT.SHIFT) != 0);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
142
|
+
/*
|
143
|
+
* @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
|
144
|
+
*/
|
145
|
+
public void mouseDoubleClick(MouseEvent event) {
|
146
|
+
fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y);
|
147
|
+
stopSelecting();
|
148
|
+
stopAutoScroll();
|
149
|
+
}
|
150
|
+
|
151
|
+
/*
|
152
|
+
* @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
|
153
|
+
*/
|
154
|
+
public void mouseMove(MouseEvent event) {
|
155
|
+
if (fIsListeningForMove && !autoScroll(event)) {
|
156
|
+
int newLine= fParentRuler.toDocumentLineNumber(event.y);
|
157
|
+
expandSelection(newLine);
|
158
|
+
}
|
159
|
+
fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y);
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Called when line drag selection started. Adds mouse move and track
|
164
|
+
* listeners to this column's control.
|
165
|
+
*
|
166
|
+
* @param expandExistingSelection if <code>true</code> the existing selection will be expanded,
|
167
|
+
* otherwise a new selection is started
|
168
|
+
*/
|
169
|
+
private void startSelecting(boolean expandExistingSelection) {
|
170
|
+
try {
|
171
|
+
|
172
|
+
// select line
|
173
|
+
IDocument document= fCachedTextViewer.getDocument();
|
174
|
+
int lineNumber= fParentRuler.getLineOfLastMouseButtonActivity();
|
175
|
+
if (expandExistingSelection && fCachedTextViewer instanceof ITextViewerExtension5
|
176
|
+
&& fCachedTextViewer.getTextWidget() != null) {
|
177
|
+
ITextViewerExtension5 extension5= ((ITextViewerExtension5)fCachedTextViewer);
|
178
|
+
// Find model curosr position
|
179
|
+
int widgetCaret= fCachedTextViewer.getTextWidget().getCaretOffset();
|
180
|
+
int modelCaret= extension5.widgetOffset2ModelOffset(widgetCaret);
|
181
|
+
// Find model selection range
|
182
|
+
Point selection= fCachedTextViewer.getSelectedRange();
|
183
|
+
// Start from tail of selection range (opposite of cursor position)
|
184
|
+
int startOffset= modelCaret == selection.x ? selection.x + selection.y : selection.x;
|
185
|
+
|
186
|
+
fStartLineNumber= document.getLineOfOffset(startOffset);
|
187
|
+
fStartLineOffset= startOffset;
|
188
|
+
|
189
|
+
expandSelection(lineNumber);
|
190
|
+
} else {
|
191
|
+
fStartLineNumber= lineNumber;
|
192
|
+
fStartLineOffset= document.getLineInformation(fStartLineNumber).getOffset();
|
193
|
+
fCachedTextViewer.setSelectedRange(fStartLineOffset, 0);
|
194
|
+
}
|
195
|
+
fCachedViewportSize= getVisibleLinesInViewport();
|
196
|
+
|
197
|
+
// prepare for drag selection
|
198
|
+
fIsListeningForMove= true;
|
199
|
+
|
200
|
+
} catch (BadLocationException x) {
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Called when line drag selection stopped. Removes all previously
|
206
|
+
* installed listeners from this column's control.
|
207
|
+
*/
|
208
|
+
private void stopSelecting() {
|
209
|
+
// drag selection stopped
|
210
|
+
fIsListeningForMove= false;
|
211
|
+
}
|
212
|
+
|
213
|
+
/**
|
214
|
+
* Expands the line selection from the remembered start line to the
|
215
|
+
* given line.
|
216
|
+
*
|
217
|
+
* @param lineNumber the line to which to expand the selection
|
218
|
+
*/
|
219
|
+
private void expandSelection(int lineNumber) {
|
220
|
+
try {
|
221
|
+
|
222
|
+
IDocument document= fCachedTextViewer.getDocument();
|
223
|
+
IRegion lineInfo= document.getLineInformation(lineNumber);
|
224
|
+
|
225
|
+
Display display= fCachedTextWidget.getDisplay();
|
226
|
+
Point absolutePosition= display.getCursorLocation();
|
227
|
+
Point relativePosition= fCachedTextWidget.toControl(absolutePosition);
|
228
|
+
|
229
|
+
int offset;
|
230
|
+
|
231
|
+
if (relativePosition.x < 0)
|
232
|
+
offset= lineInfo.getOffset();
|
233
|
+
else {
|
234
|
+
try {
|
235
|
+
int widgetOffset= fCachedTextWidget.getOffsetAtLocation(relativePosition);
|
236
|
+
Point p= fCachedTextWidget.getLocationAtOffset(widgetOffset);
|
237
|
+
if (p.x > relativePosition.x)
|
238
|
+
widgetOffset--;
|
239
|
+
|
240
|
+
// Convert to model offset
|
241
|
+
if (fCachedTextViewer instanceof ITextViewerExtension5) {
|
242
|
+
ITextViewerExtension5 extension= (ITextViewerExtension5)fCachedTextViewer;
|
243
|
+
offset= extension.widgetOffset2ModelOffset(widgetOffset);
|
244
|
+
} else
|
245
|
+
offset= widgetOffset + fCachedTextViewer.getVisibleRegion().getOffset();
|
246
|
+
|
247
|
+
} catch (IllegalArgumentException ex) {
|
248
|
+
int lineEndOffset= lineInfo.getOffset() + lineInfo.getLength();
|
249
|
+
|
250
|
+
// Convert to widget offset
|
251
|
+
int lineEndWidgetOffset;
|
252
|
+
if (fCachedTextViewer instanceof ITextViewerExtension5) {
|
253
|
+
ITextViewerExtension5 extension= (ITextViewerExtension5)fCachedTextViewer;
|
254
|
+
lineEndWidgetOffset= extension.modelOffset2WidgetOffset(lineEndOffset);
|
255
|
+
} else
|
256
|
+
lineEndWidgetOffset= lineEndOffset - fCachedTextViewer.getVisibleRegion().getOffset();
|
257
|
+
|
258
|
+
Point p= fCachedTextWidget.getLocationAtOffset(lineEndWidgetOffset);
|
259
|
+
if (p.x < relativePosition.x)
|
260
|
+
offset= lineEndOffset;
|
261
|
+
else
|
262
|
+
offset= lineInfo.getOffset();
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
int start= Math.min(fStartLineOffset, offset);
|
267
|
+
int end= Math.max(fStartLineOffset, offset);
|
268
|
+
|
269
|
+
if (lineNumber < fStartLineNumber)
|
270
|
+
fCachedTextViewer.setSelectedRange(end, start - end);
|
271
|
+
else
|
272
|
+
fCachedTextViewer.setSelectedRange(start, end - start);
|
273
|
+
|
274
|
+
} catch (BadLocationException x) {
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
/**
|
279
|
+
* Called when auto scrolling stopped. Clears the auto scroll direction.
|
280
|
+
*/
|
281
|
+
private void stopAutoScroll() {
|
282
|
+
fAutoScrollDirection= SWT.NULL;
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* Called on drag selection.
|
287
|
+
*
|
288
|
+
* @param event the mouse event caught by the mouse move listener
|
289
|
+
* @return <code>true</code> if scrolling happened, <code>false</code> otherwise
|
290
|
+
*/
|
291
|
+
private boolean autoScroll(MouseEvent event) {
|
292
|
+
Rectangle area= fCanvas.getClientArea();
|
293
|
+
|
294
|
+
if (event.y > area.height) {
|
295
|
+
autoScroll(SWT.DOWN);
|
296
|
+
return true;
|
297
|
+
}
|
298
|
+
|
299
|
+
if (event.y < 0) {
|
300
|
+
autoScroll(SWT.UP);
|
301
|
+
return true;
|
302
|
+
}
|
303
|
+
|
304
|
+
stopAutoScroll();
|
305
|
+
return false;
|
306
|
+
}
|
307
|
+
|
308
|
+
/**
|
309
|
+
* Scrolls the viewer into the given direction.
|
310
|
+
*
|
311
|
+
* @param direction the scroll direction
|
312
|
+
*/
|
313
|
+
private void autoScroll(int direction) {
|
314
|
+
|
315
|
+
if (fAutoScrollDirection == direction)
|
316
|
+
return;
|
317
|
+
|
318
|
+
final int TIMER_INTERVAL= 5;
|
319
|
+
final Display display= fCanvas.getDisplay();
|
320
|
+
Runnable timer= null;
|
321
|
+
switch (direction) {
|
322
|
+
case SWT.UP:
|
323
|
+
timer= new Runnable() {
|
324
|
+
public void run() {
|
325
|
+
if (fAutoScrollDirection == SWT.UP) {
|
326
|
+
int top= getInclusiveTopIndex();
|
327
|
+
if (top > 0) {
|
328
|
+
fCachedTextViewer.setTopIndex(top -1);
|
329
|
+
expandSelection(top -1);
|
330
|
+
display.timerExec(TIMER_INTERVAL, this);
|
331
|
+
}
|
332
|
+
}
|
333
|
+
}
|
334
|
+
};
|
335
|
+
break;
|
336
|
+
case SWT.DOWN:
|
337
|
+
timer= new Runnable() {
|
338
|
+
public void run() {
|
339
|
+
if (fAutoScrollDirection == SWT.DOWN) {
|
340
|
+
int top= getInclusiveTopIndex();
|
341
|
+
fCachedTextViewer.setTopIndex(top +1);
|
342
|
+
expandSelection(top +1 + fCachedViewportSize);
|
343
|
+
display.timerExec(TIMER_INTERVAL, this);
|
344
|
+
}
|
345
|
+
}
|
346
|
+
};
|
347
|
+
break;
|
348
|
+
}
|
349
|
+
|
350
|
+
if (timer != null) {
|
351
|
+
fAutoScrollDirection= direction;
|
352
|
+
display.timerExec(TIMER_INTERVAL, timer);
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
/**
|
357
|
+
* Returns the viewer's first visible line, even if only partially visible.
|
358
|
+
*
|
359
|
+
* @return the viewer's first visible line
|
360
|
+
*/
|
361
|
+
private int getInclusiveTopIndex() {
|
362
|
+
if (fCachedTextWidget != null && !fCachedTextWidget.isDisposed()) {
|
363
|
+
return JFaceTextUtil.getPartialTopIndex(fCachedTextViewer);
|
364
|
+
}
|
365
|
+
return -1;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
|
369
|
+
/** This column's parent ruler */
|
370
|
+
private CompositeRuler fParentRuler;
|
371
|
+
/** Cached text viewer */
|
372
|
+
private ITextViewer fCachedTextViewer;
|
373
|
+
/** Cached text widget */
|
374
|
+
private StyledText fCachedTextWidget;
|
375
|
+
/** The columns canvas */
|
376
|
+
private Canvas fCanvas;
|
377
|
+
/** Cache for the actual scroll position in pixels */
|
378
|
+
private int fScrollPos;
|
379
|
+
/** The drawable for double buffering */
|
380
|
+
private Image fBuffer;
|
381
|
+
/** The internal listener */
|
382
|
+
private InternalListener fInternalListener= new InternalListener();
|
383
|
+
/** The font of this column */
|
384
|
+
private Font fFont;
|
385
|
+
/** The indentation cache */
|
386
|
+
private int[] fIndentation;
|
387
|
+
/** Indicates whether this column reacts on text change events */
|
388
|
+
private boolean fSensitiveToTextChanges= false;
|
389
|
+
/** The foreground color */
|
390
|
+
private Color fForeground;
|
391
|
+
/** The background color */
|
392
|
+
private Color fBackground;
|
393
|
+
/** Cached number of displayed digits */
|
394
|
+
private int fCachedNumberOfDigits= -1;
|
395
|
+
/** Flag indicating whether a relayout is required */
|
396
|
+
private boolean fRelayoutRequired= false;
|
397
|
+
/**
|
398
|
+
* Redraw runnable lock
|
399
|
+
* @since 3.0
|
400
|
+
*/
|
401
|
+
private Object fRunnableLock= new Object();
|
402
|
+
/**
|
403
|
+
* Redraw runnable state
|
404
|
+
* @since 3.0
|
405
|
+
*/
|
406
|
+
private boolean fIsRunnablePosted= false;
|
407
|
+
/**
|
408
|
+
* Redraw runnable
|
409
|
+
* @since 3.0
|
410
|
+
*/
|
411
|
+
private Runnable fRunnable= new Runnable() {
|
412
|
+
public void run() {
|
413
|
+
synchronized (fRunnableLock) {
|
414
|
+
fIsRunnablePosted= false;
|
415
|
+
}
|
416
|
+
redraw();
|
417
|
+
}
|
418
|
+
};
|
419
|
+
/* @since 3.2 */
|
420
|
+
private MouseHandler fMouseHandler;
|
421
|
+
|
422
|
+
|
423
|
+
/**
|
424
|
+
* Constructs a new vertical ruler column.
|
425
|
+
*/
|
426
|
+
public LineNumberRulerColumn() {
|
427
|
+
}
|
428
|
+
|
429
|
+
/**
|
430
|
+
* Sets the foreground color of this column.
|
431
|
+
*
|
432
|
+
* @param foreground the foreground color
|
433
|
+
*/
|
434
|
+
public void setForeground(Color foreground) {
|
435
|
+
fForeground= foreground;
|
436
|
+
}
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Returns the foreground color being used to print the line numbers.
|
440
|
+
*
|
441
|
+
* @return the configured foreground color
|
442
|
+
* @since 3.0
|
443
|
+
*/
|
444
|
+
protected Color getForeground() {
|
445
|
+
return fForeground;
|
446
|
+
}
|
447
|
+
|
448
|
+
/**
|
449
|
+
* Sets the background color of this column.
|
450
|
+
*
|
451
|
+
* @param background the background color
|
452
|
+
*/
|
453
|
+
public void setBackground(Color background) {
|
454
|
+
fBackground= background;
|
455
|
+
if (fCanvas != null && !fCanvas.isDisposed())
|
456
|
+
fCanvas.setBackground(getBackground(fCanvas.getDisplay()));
|
457
|
+
}
|
458
|
+
|
459
|
+
/**
|
460
|
+
* Returns the System background color for list widgets.
|
461
|
+
*
|
462
|
+
* @param display the display
|
463
|
+
* @return the System background color for list widgets
|
464
|
+
*/
|
465
|
+
protected Color getBackground(Display display) {
|
466
|
+
if (fBackground == null)
|
467
|
+
return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
|
468
|
+
return fBackground;
|
469
|
+
}
|
470
|
+
|
471
|
+
/*
|
472
|
+
* @see IVerticalRulerColumn#getControl()
|
473
|
+
*/
|
474
|
+
public Control getControl() {
|
475
|
+
return fCanvas;
|
476
|
+
}
|
477
|
+
|
478
|
+
/*
|
479
|
+
* @see IVerticalRuleColumnr#getWidth
|
480
|
+
*/
|
481
|
+
public int getWidth() {
|
482
|
+
return fIndentation[0];
|
483
|
+
}
|
484
|
+
|
485
|
+
/**
|
486
|
+
* Computes the number of digits to be displayed. Returns
|
487
|
+
* <code>true</code> if the number of digits changed compared
|
488
|
+
* to the previous call of this method. If the method is called
|
489
|
+
* for the first time, the return value is also <code>true</code>.
|
490
|
+
*
|
491
|
+
* @return whether the number of digits has been changed
|
492
|
+
* @since 3.0
|
493
|
+
*/
|
494
|
+
protected boolean updateNumberOfDigits() {
|
495
|
+
if (fCachedTextViewer == null)
|
496
|
+
return false;
|
497
|
+
|
498
|
+
int digits= computeNumberOfDigits();
|
499
|
+
|
500
|
+
if (fCachedNumberOfDigits != digits) {
|
501
|
+
fCachedNumberOfDigits= digits;
|
502
|
+
return true;
|
503
|
+
}
|
504
|
+
|
505
|
+
return false;
|
506
|
+
}
|
507
|
+
|
508
|
+
/**
|
509
|
+
* Does the real computation of the number of digits. Subclasses may override this method if
|
510
|
+
* they need extra space on the line number ruler.
|
511
|
+
*
|
512
|
+
* @return the number of digits to be displayed on the line number ruler.
|
513
|
+
*/
|
514
|
+
protected int computeNumberOfDigits() {
|
515
|
+
IDocument document= fCachedTextViewer.getDocument();
|
516
|
+
int lines= document == null ? 0 : document.getNumberOfLines();
|
517
|
+
|
518
|
+
int digits= 2;
|
519
|
+
while (lines > Math.pow(10, digits) -1) {
|
520
|
+
++digits;
|
521
|
+
}
|
522
|
+
return Math.max(digits, 3);
|
523
|
+
}
|
524
|
+
|
525
|
+
/**
|
526
|
+
* Layouts the enclosing viewer to adapt the layout to changes of the
|
527
|
+
* size of the individual components.
|
528
|
+
*
|
529
|
+
* @param redraw <code>true</code> if this column can be redrawn
|
530
|
+
*/
|
531
|
+
protected void layout(boolean redraw) {
|
532
|
+
if (!redraw) {
|
533
|
+
fRelayoutRequired= true;
|
534
|
+
return;
|
535
|
+
}
|
536
|
+
|
537
|
+
fRelayoutRequired= false;
|
538
|
+
if (fCachedTextViewer instanceof ITextViewerExtension) {
|
539
|
+
ITextViewerExtension extension= (ITextViewerExtension) fCachedTextViewer;
|
540
|
+
Control control= extension.getControl();
|
541
|
+
if (control instanceof Composite && !control.isDisposed()) {
|
542
|
+
Composite composite= (Composite) control;
|
543
|
+
composite.layout(true);
|
544
|
+
}
|
545
|
+
}
|
546
|
+
}
|
547
|
+
|
548
|
+
/**
|
549
|
+
* Computes the indentations for the given font and stores them in
|
550
|
+
* <code>fIndentation</code>.
|
551
|
+
*/
|
552
|
+
protected void computeIndentations() {
|
553
|
+
if (fCanvas == null || fCanvas.isDisposed())
|
554
|
+
return;
|
555
|
+
|
556
|
+
GC gc= new GC(fCanvas);
|
557
|
+
try {
|
558
|
+
|
559
|
+
gc.setFont(fCanvas.getFont());
|
560
|
+
|
561
|
+
fIndentation= new int[fCachedNumberOfDigits + 1];
|
562
|
+
|
563
|
+
char[] nines= new char[fCachedNumberOfDigits];
|
564
|
+
Arrays.fill(nines, '9');
|
565
|
+
String nineString= new String(nines);
|
566
|
+
Point p= gc.stringExtent(nineString);
|
567
|
+
fIndentation[0]= p.x;
|
568
|
+
|
569
|
+
for (int i= 1; i <= fCachedNumberOfDigits; i++) {
|
570
|
+
p= gc.stringExtent(nineString.substring(0, i));
|
571
|
+
fIndentation[i]= fIndentation[0] - p.x;
|
572
|
+
}
|
573
|
+
|
574
|
+
} finally {
|
575
|
+
gc.dispose();
|
576
|
+
}
|
577
|
+
}
|
578
|
+
|
579
|
+
/*
|
580
|
+
* @see IVerticalRulerColumn#createControl(CompositeRuler, Composite)
|
581
|
+
*/
|
582
|
+
public Control createControl(CompositeRuler parentRuler, Composite parentControl) {
|
583
|
+
|
584
|
+
fParentRuler= parentRuler;
|
585
|
+
fCachedTextViewer= parentRuler.getTextViewer();
|
586
|
+
fCachedTextWidget= fCachedTextViewer.getTextWidget();
|
587
|
+
|
588
|
+
fCanvas= new Canvas(parentControl, SWT.NO_FOCUS ) {
|
589
|
+
/*
|
590
|
+
* @see org.eclipse.swt.widgets.Control#addMouseListener(org.eclipse.swt.events.MouseListener)
|
591
|
+
* @since 3.4
|
592
|
+
*/
|
593
|
+
public void addMouseListener(MouseListener listener) {
|
594
|
+
// see bug 40889, bug 230073 and AnnotationRulerColumn#isPropagatingMouseListener()
|
595
|
+
if (listener == fMouseHandler)
|
596
|
+
super.addMouseListener(listener);
|
597
|
+
else {
|
598
|
+
TypedListener typedListener= null;
|
599
|
+
if (listener != null)
|
600
|
+
typedListener= new TypedListener(listener);
|
601
|
+
addListener(SWT.MouseDoubleClick, typedListener);
|
602
|
+
}
|
603
|
+
}
|
604
|
+
};
|
605
|
+
fCanvas.setBackground(getBackground(fCanvas.getDisplay()));
|
606
|
+
fCanvas.setForeground(fForeground);
|
607
|
+
|
608
|
+
fCanvas.addPaintListener(new PaintListener() {
|
609
|
+
public void paintControl(PaintEvent event) {
|
610
|
+
if (fCachedTextViewer != null)
|
611
|
+
doubleBufferPaint(event.gc);
|
612
|
+
}
|
613
|
+
});
|
614
|
+
|
615
|
+
fCanvas.addDisposeListener(new DisposeListener() {
|
616
|
+
public void widgetDisposed(DisposeEvent e) {
|
617
|
+
handleDispose();
|
618
|
+
fCachedTextViewer= null;
|
619
|
+
fCachedTextWidget= null;
|
620
|
+
}
|
621
|
+
});
|
622
|
+
|
623
|
+
fMouseHandler= new MouseHandler();
|
624
|
+
fCanvas.addMouseListener(fMouseHandler);
|
625
|
+
fCanvas.addMouseMoveListener(fMouseHandler);
|
626
|
+
|
627
|
+
if (fCachedTextViewer != null) {
|
628
|
+
|
629
|
+
fCachedTextViewer.addViewportListener(fInternalListener);
|
630
|
+
fCachedTextViewer.addTextListener(fInternalListener);
|
631
|
+
|
632
|
+
if (fFont == null) {
|
633
|
+
if (fCachedTextWidget != null && !fCachedTextWidget.isDisposed())
|
634
|
+
fFont= fCachedTextWidget.getFont();
|
635
|
+
}
|
636
|
+
}
|
637
|
+
|
638
|
+
if (fFont != null)
|
639
|
+
fCanvas.setFont(fFont);
|
640
|
+
|
641
|
+
updateNumberOfDigits();
|
642
|
+
computeIndentations();
|
643
|
+
return fCanvas;
|
644
|
+
}
|
645
|
+
|
646
|
+
/**
|
647
|
+
* Disposes the column's resources.
|
648
|
+
*/
|
649
|
+
protected void handleDispose() {
|
650
|
+
|
651
|
+
if (fCachedTextViewer != null) {
|
652
|
+
fCachedTextViewer.removeViewportListener(fInternalListener);
|
653
|
+
fCachedTextViewer.removeTextListener(fInternalListener);
|
654
|
+
}
|
655
|
+
|
656
|
+
if (fBuffer != null) {
|
657
|
+
fBuffer.dispose();
|
658
|
+
fBuffer= null;
|
659
|
+
}
|
660
|
+
}
|
661
|
+
|
662
|
+
/**
|
663
|
+
* Double buffer drawing.
|
664
|
+
*
|
665
|
+
* @param dest the GC to draw into
|
666
|
+
*/
|
667
|
+
private void doubleBufferPaint(GC dest) {
|
668
|
+
|
669
|
+
Point size= fCanvas.getSize();
|
670
|
+
|
671
|
+
if (size.x <= 0 || size.y <= 0)
|
672
|
+
return;
|
673
|
+
|
674
|
+
if (fBuffer != null) {
|
675
|
+
Rectangle r= fBuffer.getBounds();
|
676
|
+
if (r.width != size.x || r.height != size.y) {
|
677
|
+
fBuffer.dispose();
|
678
|
+
fBuffer= null;
|
679
|
+
}
|
680
|
+
}
|
681
|
+
if (fBuffer == null)
|
682
|
+
fBuffer= new Image(fCanvas.getDisplay(), size.x, size.y);
|
683
|
+
|
684
|
+
GC gc= new GC(fBuffer);
|
685
|
+
gc.setFont(fCanvas.getFont());
|
686
|
+
if (fForeground != null)
|
687
|
+
gc.setForeground(fForeground);
|
688
|
+
|
689
|
+
try {
|
690
|
+
gc.setBackground(getBackground(fCanvas.getDisplay()));
|
691
|
+
gc.fillRectangle(0, 0, size.x, size.y);
|
692
|
+
|
693
|
+
ILineRange visibleLines= JFaceTextUtil.getVisibleModelLines(fCachedTextViewer);
|
694
|
+
if (visibleLines == null)
|
695
|
+
return;
|
696
|
+
fScrollPos= fCachedTextWidget.getTopPixel();
|
697
|
+
doPaint(gc, visibleLines);
|
698
|
+
} finally {
|
699
|
+
gc.dispose();
|
700
|
+
}
|
701
|
+
|
702
|
+
dest.drawImage(fBuffer, 0, 0);
|
703
|
+
}
|
704
|
+
|
705
|
+
/**
|
706
|
+
* Returns the view port height in lines.
|
707
|
+
*
|
708
|
+
* @return the view port height in lines
|
709
|
+
* @deprecated as of 3.2 the number of lines in the viewport cannot be computed because
|
710
|
+
* StyledText supports variable line heights
|
711
|
+
*/
|
712
|
+
protected int getVisibleLinesInViewport() {
|
713
|
+
return getVisibleLinesInViewport(fCachedTextWidget);
|
714
|
+
}
|
715
|
+
|
716
|
+
|
717
|
+
/**
|
718
|
+
* Returns <code>true</code> if the viewport displays the entire viewer contents, i.e. the
|
719
|
+
* viewer is not vertically scrollable.
|
720
|
+
*
|
721
|
+
* @return <code>true</code> if the viewport displays the entire contents, <code>false</code> otherwise
|
722
|
+
* @since 3.2
|
723
|
+
*/
|
724
|
+
protected final boolean isViewerCompletelyShown() {
|
725
|
+
return JFaceTextUtil.isShowingEntireContents(fCachedTextWidget);
|
726
|
+
}
|
727
|
+
|
728
|
+
/**
|
729
|
+
* Draws the ruler column.
|
730
|
+
*
|
731
|
+
* @param gc the GC to draw into
|
732
|
+
* @param visibleLines the visible model lines
|
733
|
+
* @since 3.2
|
734
|
+
*/
|
735
|
+
void doPaint(GC gc, ILineRange visibleLines) {
|
736
|
+
Display display= fCachedTextWidget.getDisplay();
|
737
|
+
|
738
|
+
// draw diff info
|
739
|
+
int y= -JFaceTextUtil.getHiddenTopLinePixels(fCachedTextWidget);
|
740
|
+
|
741
|
+
int lastLine= end(visibleLines);
|
742
|
+
int prevLine= -1;
|
743
|
+
int actualModelLine= -1;
|
744
|
+
for (int line= visibleLines.getStartLine(); line < lastLine; ) {
|
745
|
+
int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line);
|
746
|
+
if (widgetLine == -1)
|
747
|
+
continue;
|
748
|
+
|
749
|
+
int lineHeight= fCachedTextWidget.getLineHeight(fCachedTextWidget.getOffsetAtLine(widgetLine));
|
750
|
+
if (fCachedTextWidget.getWordWrap()) {
|
751
|
+
Point pt = new Point(5, y);
|
752
|
+
actualModelLine = fCachedTextWidget.getLineAtOffset(fCachedTextWidget.getOffsetAtLocation(pt));
|
753
|
+
if (actualModelLine != prevLine) {
|
754
|
+
paintLine(line, y, lineHeight, gc, display);
|
755
|
+
line++;
|
756
|
+
}
|
757
|
+
else {
|
758
|
+
}
|
759
|
+
prevLine = actualModelLine;
|
760
|
+
}
|
761
|
+
else {
|
762
|
+
paintLine(line, y, lineHeight, gc, display);
|
763
|
+
line++;
|
764
|
+
}
|
765
|
+
y += lineHeight;
|
766
|
+
}
|
767
|
+
}
|
768
|
+
|
769
|
+
/* @since 3.2 */
|
770
|
+
private static int end(ILineRange range) {
|
771
|
+
return range.getStartLine() + range.getNumberOfLines();
|
772
|
+
}
|
773
|
+
|
774
|
+
/**
|
775
|
+
* Computes the string to be printed for <code>line</code>. The default implementation returns
|
776
|
+
* <code>Integer.toString(line + 1)</code>.
|
777
|
+
*
|
778
|
+
* @param line the line number for which the line number string is generated
|
779
|
+
* @return the string to be printed on the line number bar for <code>line</code>
|
780
|
+
* @since 3.0
|
781
|
+
*/
|
782
|
+
protected String createDisplayString(int line) {
|
783
|
+
return Integer.toString(line + 1);
|
784
|
+
}
|
785
|
+
|
786
|
+
/**
|
787
|
+
* Returns the difference between the baseline of the widget and the
|
788
|
+
* baseline as specified by the font for <code>gc</code>. When drawing
|
789
|
+
* line numbers, the returned bias should be added to obtain text lined up
|
790
|
+
* on the correct base line of the text widget.
|
791
|
+
*
|
792
|
+
* @param gc the <code>GC</code> to get the font metrics from
|
793
|
+
* @param widgetLine the widget line
|
794
|
+
* @return the baseline bias to use when drawing text that is lined up with
|
795
|
+
* <code>fCachedTextWidget</code>
|
796
|
+
* @since 3.2
|
797
|
+
*/
|
798
|
+
private int getBaselineBias(GC gc, int widgetLine) {
|
799
|
+
/*
|
800
|
+
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=62951
|
801
|
+
* widget line height may be more than the font height used for the
|
802
|
+
* line numbers, since font styles (bold, italics...) can have larger
|
803
|
+
* font metrics than the simple font used for the numbers.
|
804
|
+
*/
|
805
|
+
int offset= fCachedTextWidget.getOffsetAtLine(widgetLine);
|
806
|
+
int widgetBaseline= fCachedTextWidget.getBaseline(offset);
|
807
|
+
|
808
|
+
FontMetrics fm= gc.getFontMetrics();
|
809
|
+
int fontBaseline= fm.getAscent() + fm.getLeading();
|
810
|
+
int baselineBias= widgetBaseline - fontBaseline;
|
811
|
+
return Math.max(0, baselineBias);
|
812
|
+
}
|
813
|
+
|
814
|
+
/**
|
815
|
+
* Paints the line. After this method is called the line numbers are painted on top
|
816
|
+
* of the result of this method.
|
817
|
+
*
|
818
|
+
* @param line the line of the document which the ruler is painted for
|
819
|
+
* @param y the y-coordinate of the box being painted for <code>line</code>, relative to <code>gc</code>
|
820
|
+
* @param lineheight the height of one line (and therefore of the box being painted)
|
821
|
+
* @param gc the drawing context the client may choose to draw on.
|
822
|
+
* @param display the display the drawing occurs on
|
823
|
+
* @since 3.0
|
824
|
+
*/
|
825
|
+
protected void paintLine(int line, int y, int lineheight, GC gc, Display display) {
|
826
|
+
int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line);
|
827
|
+
|
828
|
+
String s= createDisplayString(line);
|
829
|
+
int indentation= fIndentation[s.length()];
|
830
|
+
int baselineBias= getBaselineBias(gc, widgetLine);
|
831
|
+
gc.drawString(s, indentation, y + baselineBias, true);
|
832
|
+
}
|
833
|
+
|
834
|
+
/**
|
835
|
+
* Triggers a redraw in the display thread.
|
836
|
+
*
|
837
|
+
* @since 3.0
|
838
|
+
*/
|
839
|
+
protected final void postRedraw() {
|
840
|
+
if (fCanvas != null && !fCanvas.isDisposed()) {
|
841
|
+
Display d= fCanvas.getDisplay();
|
842
|
+
if (d != null) {
|
843
|
+
synchronized (fRunnableLock) {
|
844
|
+
if (fIsRunnablePosted)
|
845
|
+
return;
|
846
|
+
fIsRunnablePosted= true;
|
847
|
+
}
|
848
|
+
d.asyncExec(fRunnable);
|
849
|
+
}
|
850
|
+
}
|
851
|
+
}
|
852
|
+
|
853
|
+
/*
|
854
|
+
* @see IVerticalRulerColumn#redraw()
|
855
|
+
*/
|
856
|
+
public void redraw() {
|
857
|
+
|
858
|
+
if (fRelayoutRequired) {
|
859
|
+
layout(true);
|
860
|
+
return;
|
861
|
+
}
|
862
|
+
|
863
|
+
if (fCachedTextViewer != null && fCanvas != null && !fCanvas.isDisposed()) {
|
864
|
+
GC gc= new GC(fCanvas);
|
865
|
+
doubleBufferPaint(gc);
|
866
|
+
gc.dispose();
|
867
|
+
}
|
868
|
+
}
|
869
|
+
|
870
|
+
/*
|
871
|
+
* @see IVerticalRulerColumn#setModel(IAnnotationModel)
|
872
|
+
*/
|
873
|
+
public void setModel(IAnnotationModel model) {
|
874
|
+
}
|
875
|
+
|
876
|
+
/*
|
877
|
+
* @see IVerticalRulerColumn#setFont(Font)
|
878
|
+
*/
|
879
|
+
public void setFont(Font font) {
|
880
|
+
fFont= font;
|
881
|
+
if (fCanvas != null && !fCanvas.isDisposed()) {
|
882
|
+
fCanvas.setFont(fFont);
|
883
|
+
updateNumberOfDigits();
|
884
|
+
computeIndentations();
|
885
|
+
}
|
886
|
+
}
|
887
|
+
|
888
|
+
/**
|
889
|
+
* Returns the parent (composite) ruler of this ruler column.
|
890
|
+
*
|
891
|
+
* @return the parent ruler
|
892
|
+
* @since 3.0
|
893
|
+
*/
|
894
|
+
protected CompositeRuler getParentRuler() {
|
895
|
+
return fParentRuler;
|
896
|
+
}
|
897
|
+
|
898
|
+
|
899
|
+
/**
|
900
|
+
* Returns the number of lines in the view port.
|
901
|
+
*
|
902
|
+
* @param textWidget the styled text widget
|
903
|
+
* @return the number of lines visible in the view port <code>-1</code> if there's no client
|
904
|
+
* area
|
905
|
+
* @deprecated this method should not be used - it relies on the widget using a uniform line
|
906
|
+
* height
|
907
|
+
*/
|
908
|
+
static int getVisibleLinesInViewport(StyledText textWidget) {
|
909
|
+
if (textWidget != null) {
|
910
|
+
Rectangle clArea= textWidget.getClientArea();
|
911
|
+
if (!clArea.isEmpty()) {
|
912
|
+
int firstPixel= 0;
|
913
|
+
int lastPixel= clArea.height - 1; // XXX: what about margins? don't take trims as they include scrollbars
|
914
|
+
int first= JFaceTextUtil.getLineIndex(textWidget, firstPixel);
|
915
|
+
int last= JFaceTextUtil.getLineIndex(textWidget, lastPixel);
|
916
|
+
return last - first;
|
917
|
+
}
|
918
|
+
}
|
919
|
+
return -1;
|
920
|
+
}
|
921
|
+
|
922
|
+
}
|