Skip to content

Commit

Permalink
fix(search): properly display multiple search terms (npm#7980)
Browse files Browse the repository at this point in the history
When searching for multiple terms in npm, the highlighting code has a
bug where it duplicates the output any time there are matching terms.
This fixes the highlighting code.


Before:
![output of "npm search gar promisify" showing the name being
duplicated](https://github.com/user-attachments/assets/2f34ece7-7563-4db1-a540-3bb661a4c3e0)



After:
![output of "node . search gar promisify" showing the name being
displayed
correctly](https://github.com/user-attachments/assets/ba31fcd9-caf3-4a08-8bbb-7f5242f0098b)
  • Loading branch information
wraithgar authored Dec 16, 2024
1 parent a481f57 commit f7da341
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 27 deletions.
51 changes: 24 additions & 27 deletions lib/utils/format-search-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ class TextOutputStream extends Minipass {

constructor (opts) {
super()
this.#args = opts.args.map(s => s.toLowerCase()).filter(Boolean)
// Consider a search for "cowboys" and "boy". If we highlight "boys" first the "cowboys" string will no longer string match because of the ansi highlighting added to "boys". If we highlight "boy" second then the ansi reset at the end will make the highlighting only on "cowboy" with a normal "s". Neither is perfect but at least the first option doesn't do partial highlighting. So, we sort strings smaller to larger
this.#args = opts.args.map(s => s.toLowerCase()).filter(Boolean).sort((a, b) => a.length - b.length)
this.#chalk = opts.npm.chalk
this.#exclude = opts.exclude
this.#parseable = opts.parseable
Expand Down Expand Up @@ -124,38 +125,17 @@ class TextOutputStream extends Minipass {
}
}).join(' ')

let description = []
for (const arg of this.#args) {
const finder = pkg.description.toLowerCase().split(arg.toLowerCase())
let p = 0
for (const f of finder) {
description.push(pkg.description.slice(p, p + f.length))
const word = pkg.description.slice(p + f.length, p + f.length + arg.length)
description.push(this.#chalk.cyan(word))
p += f.length + arg.length
}
}
description = description.filter(Boolean)
let name = pkg.name
const description = this.#highlight(pkg.description)
let name
if (this.#args.includes(pkg.name)) {
name = this.#chalk.cyan(pkg.name)
} else {
name = []
for (const arg of this.#args) {
const finder = pkg.name.toLowerCase().split(arg.toLowerCase())
let p = 0
for (const f of finder) {
name.push(pkg.name.slice(p, p + f.length))
const word = pkg.name.slice(p + f.length, p + f.length + arg.length)
name.push(this.#chalk.cyan(word))
p += f.length + arg.length
}
}
name = this.#chalk.blue(name.join(''))
name = this.#highlight(pkg.name)
name = this.#chalk.blue(name)
}

if (description.length) {
output = `${name}\n${description.join('')}\n`
output = `${name}\n${description}\n`
} else {
output = `${name}\n`
}
Expand All @@ -171,4 +151,21 @@ class TextOutputStream extends Minipass {
output += `${this.#chalk.blue(`https://npm.im/${pkg.name}`)}\n`
return super.write(output)
}

#highlight (input) {
let output = input
for (const arg of this.#args) {
let i = output.toLowerCase().indexOf(arg)
while (i > -1) {
const highlit = this.#chalk.cyan(output.slice(i, i + arg.length))
output = [
output.slice(0, i),
highlit,
output.slice(i + arg.length),
].join('')
i = output.toLowerCase().indexOf(arg, i + highlit.length)
}
}
return output
}
}
146 changes: 146 additions & 0 deletions tap-snapshots/test/lib/commands/search.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,152 @@ Maintainers: lukekarrys
https://npm.im/pkg-no-desc
`

exports[`test/lib/commands/search.js TAP search multiple terms --color > should have expected search results with color 1`] = `
libnpm
Collection of programmatic APIs for the npm CLI
Version 3.0.1 published 2019-07-16 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm api package manager lib
https://npm.im/libnpm
libnpmaccess
programmatic library for \`npm access\` commands
Version 4.0.1 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: libnpmaccess
https://npm.im/libnpmaccess
@evocateur/libnpmaccess
programmatic library for \`npm access\` commands
Version 3.1.2 published 2019-07-16 by evocateur
Maintainers: evocateur
https://npm.im/@evocateur/libnpmaccess
@evocateur/libnpmpublish
Programmatic API for the bits behind npm publish and unpublish
Version 1.2.2 published 2019-07-16 by evocateur
Maintainers: evocateur
https://npm.im/@evocateur/libnpmpublish
libnpmorg
Programmatic api for \`npm org\` commands
Version 2.0.1 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: libnpm npm package manager api orgs teams
https://npm.im/libnpmorg
libnpmsearch
Programmatic API for searching in npm and compatible registries.
Version 3.1.0 published 2020-12-08 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm search api libnpm
https://npm.im/libnpmsearch
libnpmteam
npm Team management APIs
Version 2.0.2 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
https://npm.im/libnpmteam
libnpmpublish
Programmatic API for the bits behind npm publish and unpublish
Version 4.0.0 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
https://npm.im/libnpmpublish
libnpmfund
Programmatic API for npm fund
Version 1.0.2 published 2020-12-08 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm npmcli libnpm cli git fund gitfund
https://npm.im/libnpmfund
@npmcli/map-workspaces
Retrieves a name:pathname Map for a given workspaces config
Version 1.0.1 published 2020-09-30 by ruyadorno
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm bad map npmcli libnpm cli workspaces map-workspaces
https://npm.im/@npmcli/map-workspaces
libnpmversion
library to do the things that 'npm version' does
Version 1.0.7 published 2020-11-04 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
https://npm.im/libnpmversion
@types/libnpmsearch
TypeScript definitions for libnpmsearch
Version 2.0.1 published 2019-09-26 by types
Maintainers: types
https://npm.im/@types/libnpmsearch
pkg-no-desc
Version 1.0.0 published 2019-09-26 by lukekarrys
Maintainers: lukekarrys
https://npm.im/pkg-no-desc
`

exports[`test/lib/commands/search.js TAP search multiple terms text > should have expected search results 1`] = `
libnpm
Collection of programmatic APIs for the npm CLI
Version 3.0.1 published 2019-07-16 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm api package manager lib
https://npm.im/libnpm
libnpmaccess
programmatic library for \`npm access\` commands
Version 4.0.1 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: libnpmaccess
https://npm.im/libnpmaccess
@evocateur/libnpmaccess
programmatic library for \`npm access\` commands
Version 3.1.2 published 2019-07-16 by evocateur
Maintainers: evocateur
https://npm.im/@evocateur/libnpmaccess
@evocateur/libnpmpublish
Programmatic API for the bits behind npm publish and unpublish
Version 1.2.2 published 2019-07-16 by evocateur
Maintainers: evocateur
https://npm.im/@evocateur/libnpmpublish
libnpmorg
Programmatic api for \`npm org\` commands
Version 2.0.1 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: libnpm npm package manager api orgs teams
https://npm.im/libnpmorg
libnpmsearch
Programmatic API for searching in npm and compatible registries.
Version 3.1.0 published 2020-12-08 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm search api libnpm
https://npm.im/libnpmsearch
libnpmteam
npm Team management APIs
Version 2.0.2 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
https://npm.im/libnpmteam
libnpmpublish
Programmatic API for the bits behind npm publish and unpublish
Version 4.0.0 published 2020-11-03 by nlf
Maintainers: nlf ruyadorno darcyclarke isaacs
https://npm.im/libnpmpublish
libnpmfund
Programmatic API for npm fund
Version 1.0.2 published 2020-12-08 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm npmcli libnpm cli git fund gitfund
https://npm.im/libnpmfund
@npmcli/map-workspaces
Retrieves a name:pathname Map for a given workspaces config
Version 1.0.1 published 2020-09-30 by ruyadorno
Maintainers: nlf ruyadorno darcyclarke isaacs
Keywords: npm bad map npmcli libnpm cli workspaces map-workspaces
https://npm.im/@npmcli/map-workspaces
libnpmversion
library to do the things that 'npm version' does
Version 1.0.7 published 2020-11-04 by isaacs
Maintainers: nlf ruyadorno darcyclarke isaacs
https://npm.im/libnpmversion
@types/libnpmsearch
TypeScript definitions for libnpmsearch
Version 2.0.1 published 2019-09-26 by types
Maintainers: types
https://npm.im/@types/libnpmsearch
pkg-no-desc
Version 1.0.0 published 2019-09-26 by lukekarrys
Maintainers: lukekarrys
https://npm.im/pkg-no-desc
`

exports[`test/lib/commands/search.js TAP search no publisher > should have filtered expected search results 1`] = `
custom-registry
Version 1.0.0 published prehistoric by ???
Expand Down
24 changes: 24 additions & 0 deletions test/lib/commands/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ t.test('search', t => {
t.matchSnapshot(joinedOutput(), 'should have expected search results')
})

t.test('multiple terms text', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})

registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm', 'publish'])
t.matchSnapshot(joinedOutput(), 'should have expected search results')
})

t.test('<name> --json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
const registry = new MockRegistry({
Expand Down Expand Up @@ -68,6 +80,18 @@ t.test('search', t => {
t.matchSnapshot(joinedOutput(), 'should have expected search results with color')
})

t.test('multiple terms --color', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { color: 'always' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})

registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm', 'publish'])
t.matchSnapshot(joinedOutput(), 'should have expected search results with color')
})

t.test('/<name>/--color', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { color: 'always' } })
const registry = new MockRegistry({
Expand Down

0 comments on commit f7da341

Please sign in to comment.