diff --git a/background.js b/background.js new file mode 100644 index 0000000..f53e79f --- /dev/null +++ b/background.js @@ -0,0 +1,125 @@ +// This event is fired each time the user updates the text in the omnibox, +// as long as the extension's keyword mode is still active. +var FF_MAX_SUGGESTIONS = 20; +var FF_MAX_MATCHLENGTH = 1000; +var FF_DEBUGGING = !false; +var FF_HISTORY = []; + +function ffGetDomain(url) { + var a = document.createElement ('a'); + ; a.href = url; + return a.hostname; +} + +function ffSearchFor(text, callback) { + chrome.tabs.query({}, function(array_of_tabs) { + var fuzzy_query = new RegExp(text.split('').join('.*'), 'i'); + var exact_query = new RegExp(text, 'i'); + var exact_query_hl = new RegExp("(" + text + ")", 'ig'); + callback( + array_of_tabs. + map(function(tab) { + + if(FF_DEBUGGING) { console.debug("tab", tab); } + + switch (true) { + case !!tab.title.match(exact_query): + tab.score = 40; + tab.score += 1 - 1.0 * exact_query.exec(tab.title).index / FF_MAX_MATCHLENGTH; + tab.title = tab.title.replace(exact_query_hl, "$1") + break; + case !!tab.url.match(exact_query): + tab.score = 40; + tab.score += 1 - 1.0 * exact_query.exec(tab.url).index / FF_MAX_MATCHLENGTH; + break; + case !!tab.title.match(fuzzy_query): + tab.title = tab.title.replace(fuzzy_query, function(m) { return "" + m + ""; }) + tab.score = 20; + tab.score += 1 - 1.0 * tab.title.match(fuzzy_query)[0].length / FF_MAX_MATCHLENGTH; + break; + case !!tab.url.match(fuzzy_query): + tab.score = 20; + tab.score += 1 - 1.0 * tab.url.match(fuzzy_query)[0].length / FF_MAX_MATCHLENGTH; + break; + default: + tab.score = 0; + } + + if(tab.pinned) { tab.score += 1; } + + return tab; + }). + filter(function(tab) { + return tab.score >= 10; + }). + sort(function(tab1, tab2) { + if(tab1.score < tab2.score) return 1; + if(tab1.score > tab2.score) return -1; + return 0; + }). + slice(0, FF_MAX_SUGGESTIONS). + map(function(tab) { + + var content = JSON.stringify({tabId: tab.id, windowId: tab.windowId}); + var desc = tab.title + " " + ffGetDomain(tab.url) + ""; + if(FF_DEBUGGING) { desc = "score:" + tab.score + " - " + desc; } + + if(tab.status !== "complete") desc = "[" + tab.status + "] " + desc; + if(tab.incognito) desc = "[incognito] " + desc; + if(tab.pinned) desc = "[pinned] " + desc; + if(tab.audible) desc = "[audible] " + desc; + + return {content: content, description: desc}; + }) + ); + }); +} + +chrome.omnibox.onInputChanged.addListener( + function(text, suggest) { ffSearchFor(text, suggest); } +); + +function ffActivateTag(tabId, windowId) { + if(tabId) { + chrome.tabs.update(tabId, {active: true}); + } + if(windowId) { + chrome.windows.update(windowId, {focused: true}); + } +} + +chrome.omnibox.onInputEntered.addListener( + function(text) { + if(FF_DEBUGGING) { + console.debug("entered:", text); + console.debug("history:", FF_HISTORY); + } + + var selected = {}; + + if(text.length === 0) { + if(FF_HISTORY.length >= 2) { + selected = FF_HISTORY[FF_HISTORY.length - 2]; + } else { + return; + } + } else { + try { + selected = JSON.parse(text); + } catch(e) { + // User probably typed something but selected the first default option, + // i.e., "Run ff command: query" + ffSearchFor(text, function(suggestions) { + if(suggestions.length === 0) { return; } + var selected = JSON.parse(suggestions[0].content); + FF_HISTORY.push(selected); + ffActivateTag(selected.tabId, selected.windowId) + }); + return; + } + } + + FF_HISTORY.push(selected); + ffActivateTag(selected.tabId, selected.windowId) + } +); diff --git a/ff.png b/ff.png new file mode 100644 index 0000000..25deba7 Binary files /dev/null and b/ff.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..3572e9d --- /dev/null +++ b/manifest.json @@ -0,0 +1,17 @@ +{ + "manifest_version": 2, + + "name": "FF (Fuzzy Finder)", + "description": "Omnibox Fuzzy Finder for Chrome. Type 'ff' in your address bar and search for your tabs.", + "version": "1.0", + "author": "Sina Siadat ", + "homepage_url": "https://github.com/siadat/chrome-ff", + "permissions": [ "tabs", "activeTab" ], + "background": { + "scripts": ["background.js"] + }, + "omnibox": { "keyword" : "ff" }, + "icons": { + "16": "ff.png" + } +}