Tabbed Sketches - Suggestion or How to

I have very little experience programming, so when my Sketches start becoming longer and longer, it also becomes more tedious to scroll up and down.
I set the little red dot bookmarks and contract code sections, but still have to scroll, click and hunt a lot to find what I am looking for.

What would be nice in my opinion, would be to have tabs for different sections of the sketch...one for the code before SETUP, one for SETUP, one for LOOP, and then one tab for each subroutine...then let the IDE stitch them together.
Is that already possible?

Maybe even a special tab dedicated for variables which has a status window on the right indicating where they are defined and in what code sections they are used?
I tend to create global variables (if that's what they are called) even if unnecessary, just to avoid the compiler errors even if it creates more work to manage them.

  • I place a unique header line at the top of all major sections.
    Use <CTRL><F> and search for ====^====
    Select Find over and over until you get to this area you need, then scroll to the line.

Example


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(9600);
 
  //Reset the "Stage Prop"
  propReset();
 
  pinMode(a1, OUTPUT);
  pinMode(a2, OUTPUT);
  pinMode(b1, OUTPUT);
  pinMode(b2, OUTPUT);
  pinMode(d1, OUTPUT);
  pinMode(d2, OUTPUT);
  pinMode(d3, OUTPUT);
  pinMode(video, OUTPUT);
  pinMode(light, OUTPUT);
 
  pinMode(heartbeatLED, OUTPUT);
 
} //END of   setup()
 
 
//                                            l o o p ( )
//================================================^================================================
//
void loop()
{
  //========================================================================  T I M E R  heartbeatLED
  //Is it time to toggle the heartbeat LED ?
  if (millis() - heartbeatTime >= heartbeatInterval)
  {
    //Restart this TIMER.
    heartbeatTime = millis();
 
    //Toggle the heartbeat LED.
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

. . .


1 Like

I do that as well. I would like a simple insert command where a file could be inserted at that point. I had that years ago in my assembler and it was great, once a function was debugged I simply used the .insert command to insert it where I wanted it.

Thank you for the advice - I will give that a try.
So you do CTRL + F for the initial search then CTRL + G for the find next?
And as long as you haven't searched for anything else it defaults to the same search?

  • I often offer these three images to new users.
    ==^== is easily typed or copied into the Find: Text Box

  • Also, you can see the Find and Previous buttons.


Code Folding


Do you know that a sketch can already have multiple tabs ? What you use them for is up to you

Those red dots are not actually bookmarks, rather they denote breakpoints for those processors boards that allow debugging in the IDE

Yes, but I have only seen examples of the tabs being supplemental sketches, like the Uno R4 WiFi's example Play Animation sketch has the second tab annimation.h which it needs for the #include to run.

How do you break up an individual sketch into multiple tabs?
If I create one tab called VARIABLES, one called SETUP, one called LOOP, and one call SUBROUTINE, it doesn't all compile as a single sketch.

Good to know!
Thank you

If the filename that you give a tab has an extension of .ino then it will be combined with the main .ino file of your sketch into a single file before that file is compiled

There are more nuances to the process, such as ensuring that your variables have appropriate scope, but that is the basis

Thank you!
I will give that a try tonight and see how it goes.
I would never have thought of giving the tab a .ino extension.

Are you using IDE 2.x? Some features that may help:

  • Go to Definition
  • Go to Symbol
  • Go Back

The first two are in the right-click menu, which shows the keyboard shortcut. Go to Symbol isn't working correctly from that menu for me: the box that pops up disappears immediately. Using the keyboard works though.

Go Back is not on an obvious menu, but like everything else, it is listed by the "showCommands" command. F1 should open it. You can search for commands, and it will also show the assigned keyboard shortcut, if any. If you forget that, you can find that shortcut from the main menu: File > Advanced > Keyboard Shortcuts (or on a Mac, under the Arduino IDE menu instead of File). There's a second shortcut for the same thing, Ctrl-Alt-P; P as in Command Palette, which is the equivalent command in Visual Studio Code. (Which, if you don't do much programming, may not ring a bell.)

However, what it shows is not always accurate. Take a look at this, for instance

image
Is Ctrl + G really Go to Line/Column ?

On my Windows system that is Ctrl + L

Both Ctrl+G and Ctrl+L are listed as shortcuts under the Edit menu; +G is Find Next. Apparently assigning the shortcuts so they show in the menu has higher precedence than the underlying Keyboard Shortcuts mechanism.

Seems like a bug to me.

Hi @flynace.

Just in case you aren't already aware, I'll describe the way to add a "tab" to your sketch:

  1. Click the ●●● icon (or if you are using Arduino IDE 1.x, the icon) at the right side of the tab bar in Arduino IDE.
    A menu will open.
  2. Select "New Tab" from the menu.
    The "Name for new file" dialog will open.
  3. Type the name you want to use for the tab.
  4. Click the "OK" button.

The dialog will close and you will see that a new tab has been added to the sketch.

It is important to understand the way the code in tabs is handled:

https://arduino.github.io/arduino-cli/latest/sketch-build-process/#pre-processing

The Arduino development software performs a few transformations to your sketch before passing it to the compiler (e.g., avr-gcc):

  • All .ino and .pde files in the sketch folder (shown in the Arduino IDE as tabs with no extension) are concatenated together, starting with the file that matches the folder name followed by the others in alphabetical order.

The order of concatenation can be important. For example, let's say you created this sketch:

SuperCounter/
├── Loop.ino
├── Setup.ino
├── SuperCounter.ino
└── Variables.ino

SuperCounter.ino

/*
  This is my amazing counting sketch.

  It is organized into tabs. The code is in the other tabs.
*/

Loop.ino

void loop() {
  foo++;
}

Setup.ino

void setup() {}

Variables.ino

int foo;

The sketch will fail to compile:

Loop.ino:2:3: error: 'foo' was not declared in this scope
   foo++;
   ^~~

The reason is the order of concatenation. The code is concatenated into a single file before compilation, in this order:

SuperCounter.ino
Loop.ino
Setup.ino
Variables.ino

So the compiler sees this code:

/*
  This is my amazing counting sketch.

  It is organized into tabs. The code is in the other tabs.
*/
void loop() {
  foo++;
}
void setup() {}
int foo;

The foo variable is referenced at line 7:

  foo++;

but it is not declared until line 10:

int foo;

So the naming of the additional tabs is important. A technique you can use to control the order of concatenation, while still having freedom to give your tabs descriptive names, is to add a numeric prefix to each of the additional tab names:

SuperCounter/
├── 10_Variables.ino
├── 20_Setup.ino
├── 30_Loop.ino
└── SuperCounter.ino

I chose to increment the prefix by 10 for each tab so that I have room to insert additional tabs later without having to change the names of any tabs that should go after the new tab.

Now the concatenated code will look like this:

/*
  This is my amazing counting sketch.

  It is organized into tabs. The code is in the other tabs.
*/
int foo;
void setup() {}
void loop() {
  foo++;
}

and it will compile.

Arduino IDE displays the tabs in their order of concatenation.

It is not required. Arduino IDE will automatically give the added file the .ino file extension if you don't specify a file extension in the "Name for new file" dialog. You only need to specify the file extension if you want the added file to have an extension other than .ino.

Arduino IDE 2.x does have a feature like this. However, this is just something that comes built-in from the framework on which the IDE is built, not something the Arduino IDE developers implemented, and unfortunately it is buggy. But if you want to give it a try, I'll provide instructions:

  1. Press the Ctrl+Shift+P keyboard shortcut (Command+Shift+P for macOS users) to open the "Command Palette".
    A menu will appear on the editor toolbar:
  2. Select the "View: Toggle Outline" command from the menu.
    You can scroll down through the list of commands to find it, or type the command name in the field.

An "OUTLINE" view will now open in the left side panel of the Arduino IDE window.

1 Like

The Arduino IDE developers are tracking the bug here:

Although IDE built-in convention adds the .ino extension when none is given in the new-tab dialog, I consider the .ino file extension to indicate a stand-alone sketch. This is why I use .h extension to indicate the file is a "helper file" (my words) only containing definitions of variables, functions, notes, et c., to be used by the main sketch, or other .h helper files.

If that is the way you choose to approach your projects, fine, but there is no technical basis for such a limitation. An Arduino sketch is a folder, which may contain multiple .ino files. The system is explicitly designed to support multiple .ino files (as well as code files with other extensions if you like).

Thank you everyone for the feedback.
The quality of life improvement of using tabs in this way is amazing, and exactly what I was looking for.

@ptillisch - you should consider having a default tabbed structure template available as another option, similar to the way you have File - New - Cloud Sketch, for those that may want to organize this way.
You could implement File - New - Tabbed Sketch...

1 Like

@ptillisch - is there a way to perform a FIND across all of the tabs?
That would be very helpful, especially is you want to see where a variable defined in one tab is used in other tabs.
The OUTLINE isn't really very helpful, or I just don't understand how to use it.

And is there a way to export all of the tabs stitched together?
A FILE - EXPORT option to publish a single sketch would also be helpful so one doesn't have to cut & paste all of the tabs into a single new sketch.

Thanks again

Yes, click the magnifying glass icon on the "activity bar" at the left side of the Arduino IDE window. This will open the "SEARCH" view in the left side panel of the Arduino IDE window. That can be used to find text across the entire sketch.

You can obtain the C++ file that is generated by the Arduino sketch build system:

  1. Select File > Preferences... (or Arduino IDE > Settings... for macOS users) from the Arduino IDE menus.
    The "Preferences" dialog will open.
  2. Check the box next to "Show verbose output during: ☐ compile" in the "Preferences" dialog.
  3. Click the "OK" button.
    The "Preferences" dialog will close.
  4. Select Sketch > Verify/Compile from the Arduino IDE menus.
  5. Wait for the compilation to finish.
  6. Examine the contents of the black "Output" panel at the bottom of the Arduino IDE window. In the compilation commands printed there you will find the path of the .ino.cpp file in the temporary build folder that was created when compiling your sketch.
    For example, I see this command:
    Compiling sketch...
    "C:\\Users\\per\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino7/bin/avr-g++" -c -g -Os -Wall -Wextra -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto -mmcu=atmega2560 -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_MEGA2560 -DARDUINO_ARCH_AVR "-IC:\\Users\\per\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\avr\\1.8.6\\cores\\arduino" "-IC:\\Users\\per\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\avr\\1.8.6\\variants\\mega" "C:\\Users\\per\\AppData\\Local\\arduino\\sketches\\31E27BE23335302C68BA2AF2A4608560\\sketch\\SuperCounter.ino.cpp" -o "C:\\Users\\per\\AppData\\Local\\arduino\\sketches\\31E27BE23335302C68BA2AF2A4608560\\sketch\\SuperCounter.ino.cpp.o"
    
    
    So I know the file is located at this path on my hard drive:
    C:\Users\per\AppData\Local\arduino\sketches\31E27BE23335302C68BA2AF2A4608560\sketch\SuperCounter.ino.cpp
    

If you look at the content of that file, you will find that it is more complicated than what I represented in post #13. It will instead look something like this:

#include <Arduino.h>
#line 1 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\20_Setup.ino"
void setup();
#line 1 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\30_Loop.ino"
void loop();
#line 0 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\20_Setup.ino"
#line 1 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\SuperCounter.ino"
/*
  This is my amazing counting sketch.

  It is organized into tabs. The code is in the other tabs.
*/
#line 1 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\10_Variables.ino"
int foo;
#line 1 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\20_Setup.ino"
void setup() {}
#line 1 "C:\\Users\\per\\Documents\\Arduino\\SuperCounter\\30_Loop.ino"
void loop() {
  foo++;
}

There is explanation for this in the "Sketch Build Process" documentation:

https://arduino.github.io/arduino-cli/latest/sketch-build-process/#pre-processing

  • If not already present, #include <Arduino.h> is added to the sketch. This header file (found in the core folder for the currently selected board) includes all the definitions needed for the standard Arduino core.
  • Prototypes are generated for all function definitions in .ino/.pde files that don't already have prototypes. In some rare cases, prototype generation may fail for some functions. To work around this, you can provide your own prototypes for these functions.
  • #line directives are added to make warning or error messages reflect the original sketch layout.

I don't see any value in that.

It can be useful to look at the preprocessed code in the rare cases where valid code is not compiling due to it not being handled correctly by the Arduino sketch preprocessor.

It can also be educational or interesting to look at the code just to get a better understanding of how the Arduino sketch build system works, and what is actually being seen by the C++ compiler.

However, I can't imagine any use case where you would need to combine the files of a sketch into single file.

You seem to misunderstand what an Arduino sketch is. A sketch is a folder. The .ino file itself is not the sketch. So in the example I gave in post #13, the SuperCounter folder is the sketch. All four .ino files in that folder are only components of a single sketch.

If you want to create a new sketch from an existing multi-file sketch, just make a copy of the entire folder. You can do that in Arduino IDE by selecting File > Save As.... That will save a copy of the sketch folder, including all the files present in the parent sketch.

Thank you @ptillisch for the explanation of Search vs Find, that is also helpful.

Sorry for not understanding what a sketch is versus .ino files or code.
There are times when asking questions in the forum that members ask "Post your entire sketch" in order to be able to respond.
If your "sketch" is made up of 10 tabs of .ino components, you would have to select all, copy and paste from each tab into the response.

Since there is a Sketch - Export Compiled Binary option, I just thought it would be nice to have an automated way of exporting the combined code of all of the tabbed components (whatever you want to call that) as a single code entity for pasting into the forum (as an example).