2828
2929import java .awt .BorderLayout ;
3030import java .awt .Font ;
31+ import java .awt .Toolkit ;
3132import java .awt .event .ActionEvent ;
3233import java .awt .event .ActionListener ;
34+ import java .awt .event .InputEvent ;
35+ import java .awt .event .KeyEvent ;
36+ import java .awt .event .MouseWheelListener ;
37+ import java .awt .event .MouseWheelEvent ;
3338
3439import java .io .IOException ;
3540
3641import javax .swing .Action ;
3742import javax .swing .BorderFactory ;
43+ import javax .swing .Icon ;
44+ import javax .swing .InputMap ;
45+ import javax .swing .JComponent ;
3846import javax .swing .JMenuItem ;
3947import javax .swing .JPanel ;
4048import javax .swing .JPopupMenu ;
49+ import javax .swing .KeyStroke ;
50+ import javax .swing .SwingUtilities ;
4151import javax .swing .ToolTipManager ;
52+ import javax .swing .UIManager ;
4253import javax .swing .border .MatteBorder ;
4354import javax .swing .event .PopupMenuEvent ;
4455import javax .swing .event .PopupMenuListener ;
5061
5162import static java .nio .file .StandardWatchEventKinds .*;
5263import java .nio .file .WatchService ;
64+ import java .util .ArrayList ;
65+ import java .util .List ;
5366import java .nio .file .WatchKey ;
5467import java .nio .file .WatchEvent ;
5568import java .nio .file .FileSystems ;
6073import org .fife .ui .autocomplete .AutoCompletion ;
6174import org .fife .ui .autocomplete .DefaultCompletionProvider ;
6275import org .fife .ui .rsyntaxtextarea .RSyntaxDocument ;
76+ import org .fife .ui .rsyntaxtextarea .RSyntaxTextArea ;
6377import org .fife .ui .rsyntaxtextarea .RSyntaxTextAreaEditorKit ;
6478import org .fife .ui .rsyntaxtextarea .RSyntaxUtilities ;
6579import org .fife .ui .rtextarea .Gutter ;
80+ import org .fife .ui .rtextarea .GutterIconInfo ;
81+ import org .fife .ui .rtextarea .RTADefaultInputMap ;
82+ import org .fife .ui .rtextarea .RTextArea ;
83+ import org .fife .ui .rtextarea .RTextAreaEditorKit ;
6684import org .fife .ui .rtextarea .RTextScrollPane ;
85+ import org .fife .ui .rtextarea .RecordableTextAction ;
6786
6887import cc .arduino .UpdatableBoardsLibsFakeURLsHandler ;
6988import cc .arduino .autocomplete .ClangCompletionProvider ;
89+ import cc .arduino .autocomplete .CompletionType ;
7090import cc .arduino .autocomplete .CompletionsRenderer ;
7191import processing .app .helpers .DocumentTextChangeListener ;
92+ import processing .app .helpers .PreferencesMap ;
7293import processing .app .syntax .ArduinoTokenMakerFactory ;
7394import processing .app .syntax .PdeKeywords ;
7495import processing .app .syntax .SketchTextArea ;
@@ -152,6 +173,8 @@ private RSyntaxDocument createDocument(String contents) {
152173 return document ;
153174 }
154175
176+ public static final String rtaNextBookmarkAction = "RTA.NextBookmarkAction" ;
177+
155178 private RTextScrollPane createScrollPane (SketchTextArea textArea ) throws IOException {
156179 RTextScrollPane scrollPane = new RTextScrollPane (textArea , true );
157180 scrollPane .setBorder (new MatteBorder (0 , 6 , 0 , 0 , Theme .getColor ("editor.bgcolor" )));
@@ -160,13 +183,118 @@ private RTextScrollPane createScrollPane(SketchTextArea textArea) throws IOExcep
160183 scrollPane .setIconRowHeaderEnabled (false );
161184
162185 Gutter gutter = scrollPane .getGutter ();
163- gutter .setBookmarkingEnabled (false );
164- //gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.TEMPLATE));
186+
187+ if (PreferencesData .getBoolean ("editor.bookmarks" )) {
188+ gutter .setBookmarkingEnabled (true );
189+ gutter .setBookmarkIcon (CompletionsRenderer .getIcon (CompletionType .FUNCTION ));
190+ InputMap map = SwingUtilities .getUIInputMap (textArea , JComponent .WHEN_FOCUSED );
191+ NextBookmarkAction action = new NextBookmarkAction (rtaNextBookmarkAction );
192+ map .put (KeyStroke .getKeyStroke (KeyEvent .VK_F3 , 0 ), action );
193+ SwingUtilities .replaceUIInputMap (textArea ,JComponent .WHEN_FOCUSED ,map );
194+ }
195+
165196 gutter .setIconRowHeaderInheritsGutterBackground (true );
166197
167198 return scrollPane ;
168199 }
169200
201+ public class GutterIconInfoTabs {
202+ GutterIconInfo bookmark ;
203+ SketchFile file ;
204+ RTextArea textArea ;
205+
206+ public String toString ( ) {
207+ return "File:" + file .getFileName () + " -- Line:" + bookmark .getMarkedOffset ();
208+ }
209+ }
210+
211+ /**
212+ * Action that moves the caret to the next bookmark.
213+ */
214+ public class NextBookmarkAction extends RecordableTextAction {
215+
216+ public NextBookmarkAction (String name ) {
217+ super (name );
218+ }
219+
220+ @ Override
221+ public void actionPerformedImpl (ActionEvent e , RTextArea textArea ) {
222+
223+ Gutter gutter = RSyntaxUtilities .getGutter (textArea );
224+
225+ List <GutterIconInfoTabs > bookmarks = new ArrayList <GutterIconInfoTabs >();
226+
227+ try {
228+
229+ for (EditorTab tab : editor .getTabs ()) {
230+ gutter = RSyntaxUtilities .getGutter (tab .getTextArea ());
231+
232+ if (gutter !=null ) {
233+ GutterIconInfo [] tabBookmarks = gutter .getBookmarks ();
234+ for (GutterIconInfo element : tabBookmarks ) {
235+ GutterIconInfoTabs bookmark = new GutterIconInfoTabs ();
236+ bookmark .file = tab .getSketchFile ();
237+ bookmark .bookmark = element ;
238+ bookmark .textArea = tab .getTextArea ();
239+ bookmarks .add (bookmark );
240+ }
241+ }
242+ }
243+
244+ if (bookmarks .size ()==0 ) {
245+ UIManager .getLookAndFeel ().
246+ provideErrorFeedback (textArea );
247+ return ;
248+ }
249+
250+ GutterIconInfoTabs moveTo = null ;
251+ int curLine = textArea .getCaretLineNumber ();
252+
253+ for (int i =0 ; i <bookmarks .size (); i ++) {
254+ GutterIconInfo bookmark = bookmarks .get (i ).bookmark ;
255+ int offs = bookmark .getMarkedOffset ();
256+ int line = 0 ;
257+ int curTabIndex = editor .getCurrentTabIndex ();
258+ int bookmarkTabIndex = editor .findTabIndex (bookmarks .get (i ).file );
259+ if (curTabIndex == bookmarkTabIndex ) {
260+ line = textArea .getLineOfOffset (offs );
261+ }
262+ if ((curTabIndex == bookmarkTabIndex && line >curLine ) || (curTabIndex < bookmarkTabIndex )) {
263+ moveTo = bookmarks .get (i );
264+ break ;
265+ }
266+ }
267+ if (moveTo ==null ) { // Loop back to beginning
268+ moveTo = bookmarks .get (0 );
269+ }
270+
271+ editor .selectTab (editor .findTabIndex (moveTo .file ));
272+
273+ int offs = moveTo .bookmark .getMarkedOffset ();
274+ if (moveTo .textArea instanceof RSyntaxTextArea ) {
275+ RSyntaxTextArea rsta = (RSyntaxTextArea )moveTo .textArea ;
276+ if (rsta .isCodeFoldingEnabled ()) {
277+ rsta .getFoldManager ().
278+ ensureOffsetNotInClosedFold (offs );
279+ }
280+ }
281+ int line = moveTo .textArea .getLineOfOffset (offs );
282+ offs = moveTo .textArea .getLineStartOffset (line );
283+ moveTo .textArea .setCaretPosition (offs );
284+
285+ } catch (BadLocationException ble ) { // Never happens
286+ UIManager .getLookAndFeel ().
287+ provideErrorFeedback (textArea );
288+ ble .printStackTrace ();
289+ }
290+ }
291+
292+ @ Override
293+ public String getMacroID () {
294+ return getName ();
295+ }
296+ }
297+
170298 private SketchTextArea createTextArea (RSyntaxDocument document )
171299 throws IOException {
172300 final SketchTextArea textArea = new SketchTextArea (document , editor .base .getPdeKeywords ());
0 commit comments