Skip to content
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

Add feature move current tab to an existing window #3925

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 17 additions & 1 deletion background_scripts/commands.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ const Commands = {
"Vomnibar.activateBookmarks",
"Vomnibar.activateBookmarksInNewTab",
"Vomnibar.activateTabSelection",
"Vomnibar.moveTabToWindow",
"Vomnibar.activateEditUrl",
"Vomnibar.activateEditUrlInNewTab"],
findCommands: ["enterFindMode", "performFind", "performBackwardsFind"],
Expand All @@ -243,7 +244,12 @@ const Commands = {
"removeTab",
"restoreTab",
"moveTabToNewWindow",
"closeTabsOnLeft","closeTabsOnRight",
"closeTabsOnLeft",
"closeOneTabOnLeft",
"closeOneTabOnLeftAndSelf",
"closeTabsOnRight",
"closeOneTabOnRight",
"closeOneTabOnRightAndSelf",
"closeOtherTabs",
"moveTabLeft",
"moveTabRight"],
Expand Down Expand Up @@ -274,7 +280,11 @@ const Commands = {
"moveTabLeft",
"moveTabRight",
"closeTabsOnLeft",
"closeOneTabOnLeft",
"closeOneTabOnLeftAndSelf",
"closeTabsOnRight",
"closeOneTabOnRight",
"closeOneTabOnRightAndSelf",
"closeOtherTabs",
"enterVisualLineMode",
"toggleViewSource",
Expand Down Expand Up @@ -325,6 +335,7 @@ const defaultKeyMappings = {
"o": "Vomnibar.activate",
"O": "Vomnibar.activateInNewTab",
"T": "Vomnibar.activateTabSelection",
"w": "Vomnibar.moveTabToWindow",
"b": "Vomnibar.activateBookmarks",
"B": "Vomnibar.activateBookmarksInNewTab",
"ge": "Vomnibar.activateEditUrl",
Expand Down Expand Up @@ -437,7 +448,11 @@ const commandDescriptions = {
toggleMuteTab: ["Mute or unmute current tab", { background: true, noRepeat: true }],

closeTabsOnLeft: ["Close tabs on the left", {background: true, noRepeat: true}],
closeOneTabOnLeft: ["Close one tab on the left", {background: true}],
closeOneTabOnLeftAndSelf: ["Close one tab on the left and itself", {background: true}],
closeTabsOnRight: ["Close tabs on the right", {background: true, noRepeat: true}],
closeOneTabOnRight: ["Close one tab on the right", {background: true}],
closeOneTabOnRightAndSelf: ["Close one tab on the right and itself", {background: true}],
closeOtherTabs: ["Close all other tabs", {background: true, noRepeat: true}],

moveTabLeft: ["Move tab to the left", { background: true }],
Expand All @@ -446,6 +461,7 @@ const commandDescriptions = {
"Vomnibar.activate": ["Open URL, bookmark or history entry", { topFrame: true }],
"Vomnibar.activateInNewTab": ["Open URL, bookmark or history entry in a new tab", { topFrame: true }],
"Vomnibar.activateTabSelection": ["Search through your open tabs", { topFrame: true }],
"Vomnibar.moveTabToWindow": ["Move current tab to an existing window", { topFrame: true }],
"Vomnibar.activateBookmarks": ["Open a bookmark", { topFrame: true }],
"Vomnibar.activateBookmarksInNewTab": ["Open a bookmark in a new tab", { topFrame: true }],
"Vomnibar.activateEditUrl": ["Edit the current URL", { topFrame: true }],
Expand Down
52 changes: 52 additions & 0 deletions background_scripts/completion.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,57 @@ class TabCompleter {
}
}

// Searches through all windows, matching on title and URL.
class WindowCompleter {
filter({ name, queryTerms }, onComplete) {
if ((name !== "windows") && (queryTerms.length === 0))
return onComplete([]);

chrome.windows.getAll({populate: true}, windows => {
chrome.tabs.query({currentWindow: true, active: true}, tabs => {
const curTab = tabs[0];
// an array of active tabs for each window
const activeTabs = windows.filter(window => (curTab.windowId !== window.id) && (curTab.incognito === window.incognito))
.map(window => window.tabs.find(tab => tab.active));
const results = activeTabs.filter(activeTab => RankingUtils.matches(queryTerms, activeTab.url, activeTab.title));
const suggestions = results.map(activeTab => {
const suggestion = new Suggestion({
queryTerms,
type: "window",
url: activeTab.url,
title: activeTab.title,
tabId: curTab.id,
windowId: activeTab.windowId,
deDuplicate: false,
});
suggestion.relevancy = this.computeRelevancy(suggestion);
return suggestion;
}).sort((a, b) => b.relevancy - a.relevancy);
// Boost relevancy with a multiplier so a relevant tab doesn't
// get crowded out by results from competing completers. To
// prevent tabs from crowding out everything else in turn,
// penalize them for being further down the results list by
// scaling on a hyperbola starting at 1 and approaching 0
// asymptotically for higher indexes. The multiplier and the
// curve fall-off were objectively chosen on the grounds that
// they seem to work pretty well.
suggestions.forEach(function(suggestion,i) {
suggestion.relevancy *= 8;
return suggestion.relevancy /= ( (i / 4) + 1 );
});
onComplete(suggestions);
});
});
}

computeRelevancy(suggestion) {
if (suggestion.queryTerms.length)
return RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title);
else
return BgUtils.tabRecency.recencyScore(suggestion.windowId);
}
}

class SearchEngineCompleter {
constructor() {
this.previousSuggestions = null;
Expand Down Expand Up @@ -1091,6 +1142,7 @@ Object.assign(global, {
HistoryCompleter,
DomainCompleter,
TabCompleter,
WindowCompleter,
SearchEngineCompleter,
HistoryCache,
RankingUtils,
Expand Down
34 changes: 30 additions & 4 deletions background_scripts/main.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const completionSources = {
history: new HistoryCompleter,
domains: new DomainCompleter,
tabs: new TabCompleter,
windows: new WindowCompleter,
searchEngines: new SearchEngineCompleter
};

Expand All @@ -52,10 +53,12 @@ const completers = {
completionSources.history,
completionSources.domains,
completionSources.tabs,
completionSources.searchEngines
]),
completionSources.windows,
completionSources.searchEngines,
]),
bookmarks: new MultiCompleter([completionSources.bookmarks]),
tabs: new MultiCompleter([completionSources.tabs])
tabs: new MultiCompleter([completionSources.tabs]),
windows: new MultiCompleter([completionSources.windows]),
};

const completionHandlers = {
Expand Down Expand Up @@ -225,6 +228,16 @@ const selectSpecificTab = request => chrome.tabs.get(request.id, function(tab) {
return chrome.tabs.update(request.id, { active: true });
});

//
// Move the tab with request.tabId to the window with request.windowId
//
const moveTabToSpecificWindow = request => {
// must focus window first, otherwise focus will shift to address bar
chrome.windows.update(request.windowId, { focused: true });
chrome.tabs.move(request.tabId, {windowId: request.windowId, index: -1});
chrome.tabs.update(request.tabId, { active: true });
};

const moveTab = function({count, tab, registryEntry}) {
if (registryEntry.command === "moveTabLeft")
count = -count;
Expand Down Expand Up @@ -325,7 +338,11 @@ const BackgroundCommands = {
},

closeTabsOnLeft(request) { return removeTabsRelative("before", request); },
closeOneTabOnLeft(request) { return removeTabsRelative("beforeOne", request); },
closeOneTabOnLeftAndSelf(request) { return removeTabsRelative("beforeOneAndSelf", request); },
closeTabsOnRight(request) { return removeTabsRelative("after", request); },
closeOneTabOnRight(request) { return removeTabsRelative("afterOne", request); },
closeOneTabOnRightAndSelf(request) { return removeTabsRelative("afterOneAndSelf", request); },
closeOtherTabs(request) { return removeTabsRelative("both", request); },

visitPreviousTab({count, tab}) {
Expand Down Expand Up @@ -360,13 +377,21 @@ var forCountTabs = (count, currentTab, callback) => chrome.tabs.query({currentWi
});

// Remove tabs before, after, or either side of the currently active tab
var removeTabsRelative = (direction, {tab: activeTab}) => chrome.tabs.query({currentWindow: true}, function(tabs) {
var removeTabsRelative = (direction, {count, tab: activeTab}) => chrome.tabs.query({currentWindow: true}, function(tabs) {
const shouldDelete =
(() => { switch (direction) {
case "before":
return index => index < activeTab.index;
case "beforeOne":
return index => index < activeTab.index && index >= activeTab.index - count;
case "beforeOneAndSelf":
return index => index <= activeTab.index && index >= activeTab.index - count;
case "after":
return index => index > activeTab.index;
case "afterOne":
return index => index > activeTab.index && index <= activeTab.index + count;
case "afterOneAndSelf":
return index => index >= activeTab.index && index <= activeTab.index + count;
case "both":
return index => index !== activeTab.index;
} })();
Expand Down Expand Up @@ -629,6 +654,7 @@ var sendRequestHandlers = {
frameFocused: handleFrameFocused,
nextFrame: BackgroundCommands.nextFrame,
selectSpecificTab,
moveTabToSpecificWindow,
createMark: Marks.create.bind(Marks),
gotoMark: Marks.goto.bind(Marks),
// Send a message to all frames in the current tab.
Expand Down
5 changes: 3 additions & 2 deletions content_scripts/mode_normal.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ var NormalModeCommands = {
scrollToRight() { Scroller.scrollTo("x", "max"); },
scrollUp(count) { Scroller.scrollBy("y", -1 * Settings.get("scrollStepSize") * count); },
scrollDown(count) { Scroller.scrollBy("y", Settings.get("scrollStepSize") * count); },
scrollPageUp(count) { Scroller.scrollBy("y", "viewSize", (-1/2) * count); },
scrollPageDown(count) { Scroller.scrollBy("y", "viewSize", (1/2) * count); },
scrollPageUp(count) { Scroller.scrollBy("y", "viewSize", (-1.2/2) * count); },
scrollPageDown(count) { Scroller.scrollBy("y", "viewSize", (1.2/2) * count); },
scrollFullPageUp(count) { Scroller.scrollBy("y", "viewSize", -1 * count); },
scrollFullPageDown(count) { Scroller.scrollBy("y", "viewSize", 1 * count); },
scrollLeft(count) { Scroller.scrollBy("x", -1 * Settings.get("scrollStepSize") * count); },
Expand Down Expand Up @@ -274,6 +274,7 @@ if (typeof Vomnibar !== 'undefined') {
"Vomnibar.activate": Vomnibar.activate.bind(Vomnibar),
"Vomnibar.activateInNewTab": Vomnibar.activateInNewTab.bind(Vomnibar),
"Vomnibar.activateTabSelection": Vomnibar.activateTabSelection.bind(Vomnibar),
"Vomnibar.moveTabToWindow": Vomnibar.moveTabToWindow.bind(Vomnibar),
"Vomnibar.activateBookmarks": Vomnibar.activateBookmarks.bind(Vomnibar),
"Vomnibar.activateBookmarksInNewTab": Vomnibar.activateBookmarksInNewTab.bind(Vomnibar),
"Vomnibar.activateEditUrl": Vomnibar.activateEditUrl.bind(Vomnibar),
Expand Down
7 changes: 7 additions & 0 deletions content_scripts/vomnibar.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ const Vomnibar = {
});
},

moveTabToWindow(sourceFrameId) {
return this.open(sourceFrameId, {
completer: "windows",
selectFirst: true,
});
},

activateBookmarks(sourceFrameId) {
return this.open(sourceFrameId, {
completer: "bookmarks",
Expand Down
20 changes: 12 additions & 8 deletions pages/vomnibar.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ class BackgroundCompleter {
this.name = name;
this.completionActions = {
navigateToUrl(url) { return openInNewTab => Vomnibar.getCompleter().launchUrl(url, openInNewTab); },
switchToTab(tabId) { return () => chrome.runtime.sendMessage({handler: "selectSpecificTab", id: tabId}); }
switchToTab(tabId) { return () => chrome.runtime.sendMessage({handler: "selectSpecificTab", id: tabId}); },
moveToWindow(tabId, windowId) { return () => chrome.runtime.sendMessage({handler: "moveTabToSpecificWindow", tabId: tabId, windowId: windowId}); }
};

this.port = chrome.runtime.connect({name: "completions"});
Expand All @@ -381,14 +382,17 @@ class BackgroundCompleter {
if (msg.id === this.messageId) {
// The result objects coming from the background page will be of the form:
// { html: "", type: "", url: "", ... }
// Type will be one of [tab, bookmark, history, domain, search], or a custom search engine description.
// Type will be one of [tab, bookmark, history, domain, search, window], or a custom search engine description.
for (let result of msg.results) {
Object.assign(result, {
performAction:
result.type === "tab" ?
this.completionActions.switchToTab(result.tabId) :
this.completionActions.navigateToUrl(result.url)
});
let completionAction;
if (result.type === "tab") {
completionAction = this.completionActions.switchToTab(result.tabId);
} else if (result.type === "window") {
completionAction = this.completionActions.moveToWindow(result.tabId, result.windowId);
} else {
completionAction = this.completionActions.navigateToUrl(result.url);
}
Object.assign(result, { performAction: completionAction });
}

// Handle the message, but only if it hasn't arrived too late.
Expand Down