From baaa5569a7571532ec7fbfc938eba5afc369d2b3 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 18 Dec 2024 16:58:58 -0400 Subject: [PATCH 1/4] Use pfgrep instead of QShell grep when possible [pfgrep][pfgrep] is a fast alternative to QShell grep. It aims to be much faster and support the full spectrum of PCRE regular expression functionality. In my testing, searching for the string `Qp0l` in `QSYSINC/H` shows: - qshell grep: ~11 seconds - pfgrep v0.2: ~8 seconds - pfgrep v0.3: ~2 seconds Quite a big improvement! Questions include: - Should VS Code offer to point you to where to download pfgrep? It's not yet in the IBM repository, but we do have our own repo, RPMs available, and it can be built from source. - With faster search, doing things like regular expressions or searching across multiple files/libraries goes from tedious to quite possible. That's out of scope for this PR, but would be quite nice to add. Same deal for regular expressions instead of fixed strings. - If you do need any features in pfgrep, let me know. I can prioritize things that VS Code needs. [pfgrep]: https://github.com/SeidenGroup/pfgrep --- src/api/IBMi.ts | 3 ++- src/api/Search.ts | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 003fb276c..9e5d1d12d 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -34,7 +34,7 @@ const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures }, { path: `/QOpenSys/pkgs/bin/`, - names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`] + names: [`git`, `grep`, `tn5250`, `pfgrep`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`] }, { path: `/QSYS.LIB/`, @@ -171,6 +171,7 @@ export default class IBMi { this.remoteFeatures = { git: undefined, grep: undefined, + pfgrep: undefined, tn5250: undefined, setccsid: undefined, md5sum: undefined, diff --git a/src/api/Search.ts b/src/api/Search.ts index 57b153085..44503eb28 100644 --- a/src/api/Search.ts +++ b/src/api/Search.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import { GetMemberInfo } from '../components/getMemberInfo'; -import { IBMiMember, SearchHit, SearchResults } from '../typings'; +import { IBMiMember, SearchHit, SearchResults, CommandResult } from '../typings'; import { GlobalConfiguration } from './Configuration'; import Instance from './Instance'; import { Tools } from './Tools'; @@ -15,6 +15,8 @@ export namespace Search { let detailedMembers: IBMiMember[]|undefined; let memberFilter: string|undefined; + const pfgrep = connection.remoteFeatures.pfgrep; + if (typeof members === `string`) { memberFilter = connection.sysNameInAmerican(`${members}.MBR`); } else @@ -42,10 +44,22 @@ export namespace Search { } // Then search the members - const result = await connection.sendQsh({ - command: `/usr/bin/grep -inHR -F "${sanitizeSearchTerm(searchTerm)}" ${memberFilter}`, - directory: connection.sysNameInAmerican(`${asp}/QSYS.LIB/${library}.LIB/${sourceFile}.FILE`) - }); + var result: CommandResult | undefined = undefined; + if (pfgrep) { + // pfgrep vs. qshell grep difference: uses -r for recursion instead of -R + // (GNU/BSD grep treat them the same); we don't use recursion yet though... + const command = `${pfgrep} -inHr -F "${sanitizeSearchTerm(searchTerm)}" ${memberFilter}`; + result = await connection.sendCommand({ + command: command, + directory: connection.sysNameInAmerican(`${asp}/QSYS.LIB/${library}.LIB/${sourceFile}.FILE`) + }); + } else { + const command = `/usr/bin/grep -inHR -F "${sanitizeSearchTerm(searchTerm)}" ${memberFilter}`; + result = await connection.sendQsh({ + command: command, + directory: connection.sysNameInAmerican(`${asp}/QSYS.LIB/${library}.LIB/${sourceFile}.FILE`) + }); + } if (!result.stderr) { let hits = parseGrepOutput( From ca5228c70e47982690f9fbea15c447f2070a798b Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 18 Dec 2024 17:10:24 -0400 Subject: [PATCH 2/4] add myself to CONTRIBUTING --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c072167f3..a2ff58a37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,3 +59,4 @@ Thanks so much to everyone [who has contributed](https://github.com/codefori/vsc * [@NicolasSchindler](https://github.com/NicolasSchindler) * [@marcin-ogon](https://github.com/marcin-ogon) * [@Detrytus59](https://github.com/Detrytus59) +* [@NattyNarwhal](https://github.com/NattyNarwhal) From 4b40d4a0d9947d9ad6c4389be0f97a46585ee85d Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 18 Dec 2024 18:26:32 -0400 Subject: [PATCH 3/4] Use the -t flag to trim whitespace By default pfgrep includes the full record, including whitespace. Maybe this should be changed, but this is what it does now; use -t to match semantics of qsh grep. Note that this will need pfgrep v0.3.2; there was a bug in character trimming in previous versions. --- src/api/Search.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/Search.ts b/src/api/Search.ts index 44503eb28..f30046f5a 100644 --- a/src/api/Search.ts +++ b/src/api/Search.ts @@ -48,7 +48,8 @@ export namespace Search { if (pfgrep) { // pfgrep vs. qshell grep difference: uses -r for recursion instead of -R // (GNU/BSD grep treat them the same); we don't use recursion yet though... - const command = `${pfgrep} -inHr -F "${sanitizeSearchTerm(searchTerm)}" ${memberFilter}`; + // another difference: use -t to trim ending whitespace (pfgrep leaves intact) + const command = `${pfgrep} -inHrt -F "${sanitizeSearchTerm(searchTerm)}" ${memberFilter}`; result = await connection.sendCommand({ command: command, directory: connection.sysNameInAmerican(`${asp}/QSYS.LIB/${library}.LIB/${sourceFile}.FILE`) From 80106ccac41a85ca34f310293366abdb3c880fed Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 18 Dec 2024 19:11:44 -0400 Subject: [PATCH 4/4] pfgrep vs. qshell grep equivalency test We don't have skips I think, so this may be weird. --- src/testing/search.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/testing/search.ts b/src/testing/search.ts index b2a732b97..0a74641c2 100644 --- a/src/testing/search.ts +++ b/src/testing/search.ts @@ -50,6 +50,21 @@ export const SearchSuite: TestSuite = { assert.ok(checkNames(result.hits.map(hit => hit.path.split("/").at(-1)!))); assert.ok(result.hits.every(hit => !hit.path.endsWith(`MBR`))); } + }, + { + name: "pfgrep vs. qsh grep equivalency", test: async () => { + const pfgrep = getConnection().remoteFeatures.pfgrep; + // This test only needs to run if pfgrep is installed + if (pfgrep) { + const resultPfgrep = await Search.searchMembers(instance, "QSYSINC", "QRPGLESRC", "IBM", "CMRPG"); + getConnection().remoteFeatures.pfgrep = undefined; + const resultQsh = await Search.searchMembers(instance, "QSYSINC", "QRPGLESRC", "IBM", "CMRPG"); + getConnection().remoteFeatures.pfgrep = pfgrep; + assert.deepEqual(resultPfgrep, resultQsh); + } else { + assert.ok(true) + } + } } ] }