When working on Google Apps Script projects locally with CLASP, it can be handy to have text completion for the Apps Script classes and methods.
I’m currently shopping for a new IDE or text editor (the developer’s existential crisis) and have decided to pick up Sublime Text again after a 9-year hiatus after a mediocre three-week trial of Neovim.
After a fresh install of Sublime Text 4 here is what I needed to do:
NPM to install CLASP and the text completion package.
CLASP – The command line Apps Script tool for local development.
Git (possibly) – If the TypeScript Sublime package is missing, you must install it via Git.
Setup
Google gracefully created a text completion package that uses Definitely Typed a TypeScript repository for type definitions that can be used with both TypeScript and vanilla JavaScript.
Install the TypeScript Package for Sublime Text
Check for the TypeScript Package
First, we check if the TypeScript package has been added to Sublime Text.
Select ctrl shift P (cmd ⌘ should replace ctrl for Apple) to get the Command Pallete and start typing: Package Control: List Packages and select it.
If the TypeScript package is present, move on to adding the Apps Script type package.
Add the TypeScript Package
Head back to the Command Pallete ( ctrl shift P ) and this time start typing Package Control: Install Package.
This will load the Sublime package. Search for ‘TypeScript’. The name must be exact. If it is in the package list (It wasn’t for me) install it and move on to adding the Apps Script type package installation.
If the package is not on the list we will need to use git to clone the package from the Windows GitHub repo for the TypeScrpt-Sublime-Plugin.
Sometimes we use the gs file ending for Google Apps Script files. To get these to autocomplete we need to pair them to either JavaScript or TypeScrit.
In Sublime Text first create any .gs file and ensure you are open in that file.
Then, go to View -> Syntax -> Open all with current extension as … .
Select either JavaScript or TypeScript.
Now, when you edit the file, the Apps Script autocomplete will work as expected.
Conclusion
I’m not sure if I am going to stick with Sublime Text, but it is like slipping on a comfy pair of shoes after such a long break from it, so who knows?
I’d love to hear in the comments if you have used Sublime Text recently or are even using it for your own Apps Script projects. What do you love and not so love about it?
Oh, by the way, I recently built a little CLI tool to help update multiple Google Apps Script projects from a single source project. If this interests you, you can check it out here:
If you have ever tried to get a list of all the child files and folders of a parent folder in Google Drive, you’ve no doubt discovered that it is a slow old process. Iterating over each item in a folder and then reading the metadata of that file or folder before calling the next one can take forever!
The built-in DriveApp Class for Google Apps Script is great for working on a small number of files and folders but it just doesn’t have the functionality to retrieve specific fields in your metadata, nor does the searchFiles method or searchFolders method have the ability to isolate just the fields that you want to retrieve. Subsequently, your processing time increases significantly as it ships junk data.
This tutorial is my attempt at, as the kiddies say today, creating a ‘blazingly fast’ file and folder iterator with Google’s Drive API v2 for Apps Script.
All examples search a selected sheet tab. However, you can easily modify the script to search a specific range or the entire Google Sheets workbook. You can learn more about how to do this here:
Find and activate the first or last row in a Google Sheet with Apps Script
Selects the row that contains the first found value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Selects the row that contains the first found value.
* Single sheet
* @param {string} text - the text to find.
* @param {string} sheetName - the target sheet.
*/
functionselectFirstRowWithVal(text,sheetName){
console.log(text,sheetName)
constss=SpreadsheetApp.getActiveSpreadsheet();
constsheet=ss.getSheetByName(sheetName);
consttextFinder=sheet.createTextFinder(text);
constcell=textFinder.findNext();
// const cell = textFinder.findPrevious();
constrow=cell.getRow();
sheet
.getRange(`A${row}:${row}`)
.activate();
};
Lines 10-12 – In this scenario, we activate the first (findNext()) or last (findPrevious())found row based on a search item that can be found anywhere in the row. We then store this cell range in the cell variable.
Line 19-21 – Next, we can activate the range of the selected row using the activate method.
We set the entire range by using A1-notation here within a template literal (that is a string within backticks ()). In Google Sheets, we can set the full width of a sheet by leaving the last column value blank in the notation, for example:
A1:1 or even 1:1.
Incidentally, you can also select a portion of the width by including an end letter in the range, for example:
A1:K1 or in the script .getRange(A${row}:K${row}`)
You can learn more about selecting the first or last found values in this tutorial:
Find and activate all rows in a Google Sheet with Apps Script
Select all rows that contains the selected value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Select all rows that contains the selected value.
* Single sheet
* @param {string} text - the text to find.
* @param {string} sheetName - the target sheet.
*/
functionselectAllRowsWithVal(text,sheetName){
constss=SpreadsheetApp.getActiveSpreadsheet();
constsheet=ss.getSheetByName(sheetName);
consttextFinder=sheet.createTextFinder(text);
constallOccurrences=textFinder.findAll();
constranges=allOccurrences
.map(item=>`A${item.getRow()}:${item.getRow()}`);
sheet.getRangeList(ranges).activate();
};
In this example, we select all of the rows containing the target search value.
Line 13 – First, we modify the Text Finder variable to the findAll method and rename the variable. This will return an array of all the cells where the values are found as an array.
Lines 15-16 – Now, we can map over each item in the array creating a new array of ranges that contain A1-notation of the selected row.
Find and format all rows in a Google Sheet with Apps Script
Format all rows that contains the selected value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Format all rows that contains the selected value.
* Single sheet
* @param {string} text - the text to find.
* @param {string} sheetName - the target sheet.
*/
functionformatAllRowsWithVal(text,sheetName){
constss=SpreadsheetApp.getActiveSpreadsheet();
constsheet=ss.getSheetByName(sheetName);
consttextFinder=sheet.createTextFinder(text);
constallOccurrences=textFinder.findAll();
constranges=allOccurrences
.map(item=>`A${item.getRow()}:${item.getRow()}`);
sheet.getRangeList(ranges)
.setBackground('#0b5394')
.setFontColor('white')
.setFontWeight('bold')
};
This last function is virtually the same as the previous one. However, we are doing something much more useful, we are programmatically updating the format of the selected rows.
Lines 18-21 – Using the getRangeList method, we can modify the formatting of any range in the list. In our example, we set the background of the row to a dark blue, changed the text colour to white and bolded the font.
More formatting methods you can use.
Here is a list of common formatting methods that you can apply to a range list:
Clear Format – clearFormat() – Clears the formatting of the selected range.
Set background Colour – setBackgroundColor(colour) – Use CSS notation like hexadecimal colours, colour names or RGB colours
Set font Colour – setFontColor(colour)– Use CSS notation like hexadecimal colours, colour names or RGB colours.
Set font Weight– setFontWeight(type) – Either “bold” or “normal”.
Set font Size= setFontSize(size) – The font size as a number.
Set font Style – setFontStyle(style) – The style, e.g. “Helvetica”, “Poppins”.
Set text rotation – setTextRotation() – Sets the rotation of the text in each cell in degrees from the original horizontal plain.
In the next tutorial, we will cover how to hide and unhide rows based on found values in a range. The process is a little different, so well worth checking out.
Subscribe to get an email when the next tutorial in this series comes out or get regular updates on my latest tutorials.
Inspired by research into a recent blog post, the Google Apps Script Text Finder Class’ Find All (findAll()) and Find Next (findNext()) methods were benchmarked over two different datasets containing 50,000 rows. The first dataset contained 1,000 cells matching the search text. The second dataset contained 100 matching cells.
For each dataset, a test was conducted to retrieve either the first 10 matching cells or the first 100 matching cells. The Find All and Find Next approaches were tested and compared on each test.
It was expected that Find Next would perform best on the condition where the dataset contained a large number of found items and only a small number of first cells needed to be reported. The benchmark results suggest that this hypothesis is most likely.
First number of cells to retrieve
Test
Function
Avg. run time over 100 runs.
Fastest Function
Fastest Avg. Time
Avg. time Difference
1000 items to find
10
1
v2 findAll
1626.24
v3 findNext
1368.45
257.79
v3 findNext
1368.45
50
2
v2 findAll
1578.19
v2 findAll
1578.19
4993.61
v3 findNext
6571.8
100 items to find
10
3
v2 findAll
360.94
v2 findAll
360.94
975.16
v3 findNext
1336.1
50
4
v2 findAll
377.13
v2 findAll
377.13
6175.59
v3 findNext
6552.72
Table: The average time in milliseconds of 100 runs of each test of Apps Script Text Finder findAll() and findNext() methods. Image link for mobile-friendly viewers.
Method
Sample Data
Two columns of data 50,000 rows deep were generated for this test. Each cell in each column consisted of a number; either 1, 2, 3, 4 or 5. An equal spread of numbers 1 through 4 where added to each row. Each column differs by the number of 5s in each row:
Col A: 1,000 5’s
Col B: 100 5’s
Each column was then selected and randomised with: Data > Randomise range.
Test
Two functions are compared to test their performance based on four test conditions based on 100 runs of each test:
Retrieve the first 10 cells containing the search text where the range contains 1,000 matching search items.
Retrieve the first 50 cells containing the search text where the range contains 1,000 matching search items.
Retrieve the first 10 cells containing the search text where the range contains 100 matching search items.
Retrieve the first 50 cells containing the search text where the range contains 100 matching search items.
The time in milliseconds was recorded using the JavaScript Date.now() method before and after the functions were run. The difference in time in milliseconds was then appended to an array and added to a Google Sheet column for each test type. This culminated in 8 sets of 100 results.
The average of each test was then recorded and used to compare performance.
Note: Performance.now() is not available in Google Apps Script.
Code
All code and results can be found copied from this sheet:
This function retrieves the full list of all found cells using the findAll() method from the Text Finder Class. All available found items in the range are then stored in the found variable.
It then relies on a for-loop to iterate through each cell and collect the cell location using the Spreadsheet App Class’ range getA1Notation method. Each cell location is then stored in the locations variable as an array item before returning the array to the initialising function.
The for-loop breaks when the total number of required cell items (the position) equal the index variable (i) in the loop.
Apps Script Text Finder Find All
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Finds the first set of matching cells of n in length or less.
* @param {string} sheetName - the name of the sheet tab.
* @param {string|number} text - text to search.
* @param {number} n - the max number of items to report.
* @param {string} rangeLoc - A1- Notation of selected range.
* @return {array} - Array of cell positions of the found match.
*/
functiontest_v2(sheetName,text,n,rangeLoc){
constposition=n-1;
constss=SpreadsheetApp.getActiveSpreadsheet();
constsheet=ss.getSheetByName(sheetName);
constrange=sheet.getRange(rangeLoc)
constfound=range.createTextFinder(text)
.findAll();
let locations=[];
for(leti=0;i<found.length;i++){
constcell=found[i];
locations.push(cell.getA1Notation())
if(i===position)break;
}
returnlocations;
}
test_v3 – Google Apps Script Text Finder Class- findNext()
In this function, a call is made to the spreadsheet to retrieve the found cell value each time findNext() method of the Text Finder Class is called. On each iteration, the getA1Notation method is used to retrieve the cell location. This location is then stored as an array value in the locations variable before being returned to the initiating function.
The function used a while-loop to iterate through each next item found until the counter – or the number of required cells to collect – is reached.
Apps Script Text Finder Find Next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Finds the first set of matching cells of n in length or less.
* @param {string} sheetName - the name of the sheet tab.
* @param {string|number} text - text to search.
* @param {number} n - the max number of items to report.
* @param {string} rangeLoc - A1- Notation of selected range.
* @return {array} - Array of cell positions of the found match.
*/
functiontest_v3(sheetName,text,n,rangeLoc){
constss=SpreadsheetApp.getActiveSpreadsheet();
constsheet=ss.getSheetByName(sheetName);
constrange=sheet.getRange(rangeLoc)
constfinder=range.createTextFinder(text)
let locations=[];
let counter=n;
while(counter>0){
let found="";
found=finder
.findNext()
.getA1Notation();
locations.push(found);
--counter;
};
returnlocations
};
Results & Discussion
Test 1: Retrieve the first 10 cells containing the search text where the range contains 1,000 matching search items.
Version 3 –findNext() performed better on average when there were 1000 potential items to find in the range but only the first 10 items need to be selected. Versions 3’s average speed was 1368.45ms compared to version 2’s average run speed of 1826.24ms. This is a performance increase of 257.79ms for version 3.
Version 2’s lower performance is likely due to needing to collect all available found cells before it can extract the top 10 items.
Version 3, makes 10 calls to the Google Sheets in this example. Compared to version 2, this takes relatively less time than collecting all available found cell references to the search item.
Test 2: Retrieve the first 50 cells containing the search text where the range contains 1,000 matching search items.
Version 2 – findAll() performed significantly better over 100 runs than version 3 when retrieving the top 50 found cells from a possible 1000. Version 2 was, on average, 4993.61ms faster at an average runtime of 1578.19ms compared to version 3’s sluggish 6571.80ms average.
It was expected that test one and test two’s times for version 2 would be similar and there are only 48.05ms between their average runtimes.
Version 3’s poor performance is likely due to its reliance on calling the spreadsheet to collect the cell data on all 50 calls it needs to make.
Test 3: Retrieve the first 10 cells containing the search text where the range contains 100 matching search items.
Version 2, again, performed better by 975.16ms than version 3 when there was a smaller potential number of items to find in the range and only the first ten items need to be retrieved.
Here the performance margin between the two versions was closer than in the previous test. Version 2’s average run speed was 360.94ms while version 3’s runtime was 1336.10ms.
With a smaller number of retrieved items, the version 2 findAll() function did not have to work as hard to collect the methods related to each range it collects. Whereas version 3 still needed to make 10 performance-intensive calls back to the Google Sheet each time with relatively no performance change to test one.
Test 4: Retrieve the first 50 cells containing the search text where the range contains 100 matching search items.
Predictably, version 2 – findAll() performed the best when the expected match sample is small (100 available matches) and the total first set of cells to retrieve was relatively large (50).
Version 2’s average completion time was 377.13ms compared to version 3’s average of 6552.72ms, performing on average 6175.59ms faster. This is by far the largest margin on performance between the two versions.
Here again, version 3 must perform 50 calls to the Google Sheet, each one retrieving the cell range data. Alternatively, version 2 makes one call to the spreadsheet and then retrieves the cell data for all collected values. This is significantly faster than version 3’s approach.
Overall
On datasets that may have the potential to contain a large number of matching items, but fewer required results to return, version 3 may be the best option. In all other cases, version 2 is the most optimal approach to finding data in a range.
It is important to note that it can be difficult to accurately measure performance with Apps Script runs because resource allocation to run a script does seem to vary. Nevertheless, with a sample size of 100 runs, it is hoped that average values will be more accurate than a smaller sample.
My team of experts can help you with all of your needs, from custom app development to integrations and security. We have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
In this article, we will look at Google Chat Apps and go over some of the basics of developing a chat app with Google Apps Script.
Note that Chat Apps are currently only available to Google Workspace paid accounts and not standard ‘@gmail’ consumer accounts.
What are Google Chat Apps?
Google Chat is a business-level messaging tool for Google Workspace that allows you to chat one-on-one and in groups called Spaces within your business and even with clients and contractors outside of your domain.
Its integrations with Google Workspace tools like Google sheets, docs and slides right inside chat along with a Google Task tab and a convenient file library tab make it a really handy business tool.
But one thing that is often overlooked is that you can add Chat Apps or Chat Bots to your Google Chat Spaces or DM a chat app directly to accomplish tasks conveniently.
There are hundreds of 3rd party Chat App integrations for you to choose from. Anything from project management to customer support and more.