Skip to content

feat(completion): adds man page search, completion #4091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions background_scripts/completion_engines.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ class BaseEngine {

// Several Google completion engines package responses as XML. This parses such XML.
class GoogleXMLBaseEngine extends BaseEngine {
parse(xhr) {
return Array.from(xhr.responseXML.getElementsByTagName("suggestion"))
.map(suggestion => suggestion.getAttribute("data"))
.filter(suggestion => suggestion);
parse(body) {
const parser = new DOMParser();
const xml = parser.parseFromString(body, 'application/xml');
return Array.from(xml.getElementsByTagName("suggestion"))
.map(suggestion => suggestion.getAttribute("data"))
.filter(suggestion => suggestion);
}
}

Expand Down Expand Up @@ -70,7 +72,7 @@ class GoogleMaps extends GoogleXMLBaseEngine {
searchUrl: "https://www.google.com/maps?q=%s",
keyword: "m",
explanation:
`\
`\
This uses regular Google completion, but prepends the text "<tt>map of</tt>" to the query. It works
well for places, countries, states, geographical regions and the like, but will not perform address
search.\
Expand All @@ -79,8 +81,8 @@ search.\
});
}

parse(xhr) {
return Array.from(super.parse(xhr))
parse(body) {
return Array.from(super.parse(body))
.filter(suggestion => suggestion.startsWith(GoogleMaps.prefix))
.map(suggestion => suggestion.slice(GoogleMaps.prefix.length));
}
Expand All @@ -100,6 +102,22 @@ class Youtube extends GoogleXMLBaseEngine {
});
}
}
class ManKier extends BaseEngine {
constructor() {
super({
engineUrl: "https://www.mankier.com/api/v2/mans/?q=%s",
regexps: ["^https?://www\\.mankier\\.com"],
example: {
searchUrl: "https://www.mankier.com/?q=%s",
keyword: "ma"
}
});
}

parse(body) {
return JSON.parse(body).results.map((res) => res.name);
}
}

class Wikipedia extends BaseEngine {
constructor() {
Expand All @@ -113,7 +131,7 @@ class Wikipedia extends BaseEngine {
});
}

parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
parse(body) { return JSON.parse(body)[1]; }
}

class Bing extends BaseEngine {
Expand All @@ -128,7 +146,7 @@ class Bing extends BaseEngine {
});
}

parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
parse(body) { return JSON.parse(body)[1]; }
}

class Amazon extends BaseEngine {
Expand All @@ -143,7 +161,7 @@ class Amazon extends BaseEngine {
});
}

parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
parse(body) { return JSON.parse(body)[1]; }
}

class AmazonJapan extends BaseEngine {
Expand All @@ -158,7 +176,7 @@ class AmazonJapan extends BaseEngine {
});
}

parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
parse(body) { return JSON.parse(body)[1]; }
}

class DuckDuckGo extends BaseEngine {
Expand All @@ -173,8 +191,8 @@ class DuckDuckGo extends BaseEngine {
});
}

parse(xhr) {
return Array.from(JSON.parse(xhr.responseText)).map((suggestion) => suggestion.phrase);
parse(body) {
return Array.from(JSON.parse(body)).map((suggestion) => suggestion.phrase);
}
}

Expand All @@ -191,8 +209,8 @@ class Webster extends BaseEngine {
});
}

parse(xhr) {
return Array.from(JSON.parse(xhr.responseText).docs).map((suggestion) => suggestion.word);
parse(body) {
return Array.from(JSON.parse(body).docs).map((suggestion) => suggestion.word);
}
}

Expand All @@ -208,8 +226,8 @@ class Qwant extends BaseEngine {
});
}

parse(xhr) {
return Array.from(JSON.parse(xhr.responseText).data.items).map((suggestion) => suggestion.value);
parse(body) {
return Array.from(JSON.parse(body).data.items).map((suggestion) => suggestion.value);
}
}

Expand All @@ -225,7 +243,7 @@ class UpToDate extends BaseEngine {
});
}

parse(xhr) { return JSON.parse(xhr.responseText).data.searchTerms; }
parse(body) { return JSON.parse(body).data.searchTerms; }
}

// A dummy search engine which is guaranteed to match any search URL, but never produces completions. This
Expand All @@ -252,6 +270,7 @@ const CompletionEngines = [
Webster,
Qwant,
UpToDate,
ManKier,
DummyCompletionEngine
];

Expand Down
25 changes: 10 additions & 15 deletions background_scripts/completion_search.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class EnginePrefixWrapper {
return this.engine.getUrl(queryTerms);
}

parse(xhr) {
return this.postprocessSuggestions(this.engine.parse(xhr));
parse(body) {
return this.postprocessSuggestions(this.engine.parse(body));
}

postprocessSuggestions(suggestions) { return suggestions; }
Expand All @@ -46,22 +46,17 @@ const CompletionSearch = {
completionCache: new SimpleCache(2 * 60 * 60 * 1000, 5000), // Two hours, 5000 entries.
engineCache:new SimpleCache(1000 * 60 * 60 * 1000), // 1000 hours.


// The amount of time to wait for new requests before launching the current request (for example, if the user
// is still typing).
delay: 100,

get(searchUrl, url, callback) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.timeout = 2500;
// According to https://xhr.spec.whatwg.org/#request-error-steps,
// readystatechange always gets called whether a request succeeds or not,
// and the `readyState == 4` means an associated `state` is "done", which is true even if any error happens
xhr.onreadystatechange = function() {
if (xhr.readyState === 4)
return callback(xhr.status === 200 ? xhr : null);
};
return xhr.send();
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 2500);
return fetch(url, { method: 'GET', mode: 'cors', signal: controller.signal })
.then((response) => clearTimeout(id) && response.status !== 200 ? callback(null) : response.text())
.then((body) => callback(body));
},

// Look up the completion engine for this searchUrl. Because of DummyCompletionEngine, we know there will
Expand Down Expand Up @@ -163,13 +158,13 @@ const CompletionSearch = {
const url = engine.getUrl(queryTerms);

// TODO(philc): Do we need to return the result of this.get here, or can we remove this return statement?
return this.get(searchUrl, url, (xhr = null) => {
return this.get(searchUrl, url, (body = null) => {
// Parsing the response may fail if we receive an unexpected or an unexpectedly-formatted response.
// In all cases, we fall back to the catch clause, below. Therefore, we "fail safe" in the case of
// incorrect or out-of-date completion engines.
let suggestions;
try {
suggestions = engine.parse(xhr)
suggestions = engine.parse(body)
// Make all suggestions lower case. It looks odd when suggestions from one completion engine are
// upper case, and those from another are lower case.
.map(s => s.toLowerCase())
Expand Down
1 change: 1 addition & 0 deletions lib/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ w: https://www.wikipedia.org/w/index.php?title=Special:Search&search=%s Wikipedi
# b: https://www.bing.com/search?q=%s Bing
# d: https://duckduckgo.com/?q=%s DuckDuckGo
# az: https://www.amazon.com/s/?field-keywords=%s Amazon
# ma: https://www.mankier.com/?q=%s ManKier
# qw: https://www.qwant.com/?q=%s Qwant\
`,
newTabUrl: "about:newtab",
Expand Down