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 ;
3336import java .awt .event .MouseWheelListener ;
3437import java .awt .event .MouseWheelEvent ;
3538
3639import java .io .IOException ;
3740
3841import javax .swing .Action ;
3942import javax .swing .BorderFactory ;
43+ import javax .swing .Icon ;
44+ import javax .swing .InputMap ;
45+ import javax .swing .JComponent ;
4046import javax .swing .JMenuItem ;
4147import javax .swing .JPanel ;
4248import javax .swing .JPopupMenu ;
49+ import javax .swing .KeyStroke ;
50+ import javax .swing .SwingUtilities ;
4351import javax .swing .ToolTipManager ;
52+ import javax .swing .UIManager ;
4453import javax .swing .border .MatteBorder ;
4554import javax .swing .event .PopupMenuEvent ;
4655import javax .swing .event .PopupMenuListener ;
5261
5362import static java .nio .file .StandardWatchEventKinds .*;
5463import java .nio .file .WatchService ;
64+ import java .util .ArrayList ;
65+ import java .util .List ;
5566import java .nio .file .WatchKey ;
5667import java .nio .file .WatchEvent ;
5768import java .nio .file .FileSystems ;
6273import org .fife .ui .autocomplete .AutoCompletion ;
6374import org .fife .ui .autocomplete .DefaultCompletionProvider ;
6475import org .fife .ui .rsyntaxtextarea .RSyntaxDocument ;
76+ import org .fife .ui .rsyntaxtextarea .RSyntaxTextArea ;
6577import org .fife .ui .rsyntaxtextarea .RSyntaxTextAreaEditorKit ;
6678import org .fife .ui .rsyntaxtextarea .RSyntaxUtilities ;
6779import 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 ;
6884import org .fife .ui .rtextarea .RTextScrollPane ;
85+ import org .fife .ui .rtextarea .RecordableTextAction ;
6986
7087import cc .arduino .UpdatableBoardsLibsFakeURLsHandler ;
7188import cc .arduino .autocomplete .ClangCompletionProvider ;
89+ import cc .arduino .autocomplete .CompletionType ;
7290import cc .arduino .autocomplete .CompletionsRenderer ;
7391import processing .app .helpers .DocumentTextChangeListener ;
92+ import processing .app .helpers .PreferencesMap ;
7493import processing .app .syntax .ArduinoTokenMakerFactory ;
7594import processing .app .syntax .PdeKeywords ;
7695import processing .app .syntax .SketchTextArea ;
@@ -154,6 +173,8 @@ private RSyntaxDocument createDocument(String contents) {
154173 return document ;
155174 }
156175
176+ public static final String rtaNextBookmarkAction = "RTA.NextBookmarkAction" ;
177+
157178 private RTextScrollPane createScrollPane (SketchTextArea textArea ) throws IOException {
158179 RTextScrollPane scrollPane = new RTextScrollPane (textArea , true );
159180 scrollPane .setBorder (new MatteBorder (0 , 6 , 0 , 0 , Theme .getColor ("editor.bgcolor" )));
@@ -162,13 +183,118 @@ private RTextScrollPane createScrollPane(SketchTextArea textArea) throws IOExcep
162183 scrollPane .setIconRowHeaderEnabled (false );
163184
164185 Gutter gutter = scrollPane .getGutter ();
165- gutter .setBookmarkingEnabled (false );
166- //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+
167196 gutter .setIconRowHeaderInheritsGutterBackground (true );
168197
169198 return scrollPane ;
170199 }
171200
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+
172298 private SketchTextArea createTextArea (RSyntaxDocument document )
173299 throws IOException {
174300 final SketchTextArea textArea = new SketchTextArea (document , editor .base .getPdeKeywords ());
0 commit comments