<commit_before><html>
<head>
<script type="text/javascript" charset="utf-8">
  var tabQueue = []; // Queue of tabs to restore with scroll info
  var keyQueue = ""; // Queue of keys typed

  // Port handler mapping
  var portHandlers = { "keyDown":              handleKeyDown,
                       "returnScrollPosition": handleReturnScrollPosition };

  // Event handlers
  var selectionChangedHandlers = [];
  var getScrollPositionHandlers = {}; // tabId -> function (tab, scrollX, scrollY);
  var tabLoadedHandlers = {}; // tabId -> function ()

  chrome.extension.onConnect.addListener(function(port, name) {
    var senderTabId = port.sender.tab ? port.sender.tab.id : null;
    // If this is a tab we've been waiting to open, execute any "tab loaded" handlers, e.g. to restore
    // the tab's scroll position.
    if (senderTabId && tabLoadedHandlers[senderTabId]) {
      var toCall = tabLoadedHandlers[senderTabId];
      // Delete first to be sure there's no circular events.
      delete tabLoadedHandlers[senderTabId];
      toCall.call();
    }

    if (portHandlers[port.name])
      port.onMessage.addListener(portHandlers[port.name]);

  });

  function handleReturnScrollPosition(args) {
    if (getScrollPositionHandlers[args.currentTab.id]) {
      // Delete first to be sure there's no circular events.
      var toCall = getScrollPositionHandlers[args.currentTab.id];
      delete getScrollPositionHandlers[args.currentTab.id];
      toCall(args.currentTab, args.scrollX, args.scrollY);
    }
  }

  chrome.tabs.onSelectionChanged.addListener(function (tabId, selectionInfo) {
    if (selectionChangedHandlers.length > 0) { selectionChangedHandlers.pop().call(); }
  });

  function repeatFunction(func, totalCount, currentCount) {
    if (currentCount < totalCount)
      func(function () { repeatFunction(func, totalCount, currentCount + 1); });
  }

  // Returns the currently selected tab along with scroll coordinates. Pass in a callback of the form:
  //   function (tab, scrollX, scrollY) { .. }
  function getCurrentTabWithScrollPosition(callback) {
    chrome.tabs.getSelected(null, function(tab) {
      getScrollPositionHandlers[tab.id] = callback;
      var scrollPort = chrome.tabs.connect(tab.id, { name: "getScrollPosition" });
      scrollPort.postMessage({currentTab: tab});
    });
  }

  // Start action functions
  function createTab(callback) {
    chrome.tabs.create({}, function(tab) { callback(); });
  }

  function nextTab() { selectTab("next"); }
  function previousTab() { selectTab("previous"); }

  /*
   * Selects a tab before or after the currently selected tab. Direction is either "next" or "previous".
   */
  function selectTab(direction) {
    chrome.tabs.getAllInWindow(null, function(tabs) {
      if (tabs.length <= 1)
        return;
      for (var i = 0; i < tabs.length; i++) {
        if (tabs[i].selected) {
          var delta = (direction == "next") ? 1 : -1;
          var toSelect = tabs[(i + delta + tabs.length) % tabs.length];
          chrome.tabs.update(toSelect.id, { selected: true });
          break;
        }
      }
    });
  }

  function removeTab(callback) {
    getCurrentTabWithScrollPosition(function(tab, scrollX, scrollY) {
                                      tabQueue.push({ tabUrl: tab.url, scrollX: scrollX, scrollY: scrollY });
                                      chrome.tabs.remove(tab.id);
                                      // We can't just call the callback here because we actually need to wait
                                      // for the selection to change to consider this action done.
                                      selectionChangedHandlers.push(callback);
                                    });
  }

  function restoreTab(callback) {
    if (tabQueue.length > 0) {
      var tabQueueEntry = tabQueue.pop();

      // We have to chain a few callbacks to set the appropriate scroll position. We can't just wait until the
      // tab is created because the content script is not available during the "loading" state. We need to
      // wait until that's over before we can call setScrollPosition.
      chrome.tabs.create({ url: tabQueueEntry.tabUrl }, function(tab) {
        tabLoadedHandlers[tab.id] = function() {
          var scrollPort = chrome.tabs.connect(tab.id, {name: "setScrollPosition"});
          scrollPort.postMessage({ scrollX: tabQueueEntry.scrollX, scrollY: tabQueueEntry.scrollY });
        };

        callback();
      });
    }
  }
  // End action functions

  var keyToCommandRegistry = {};
  keyToCommandRegistry['j']  = 'scrollDown';
  keyToCommandRegistry['k']  = 'scrollUp';
  keyToCommandRegistry['h']  = 'scrollLeft';
  keyToCommandRegistry['l']  = 'scrollRight';
  keyToCommandRegistry['gg'] = 'scrollToTop';
  keyToCommandRegistry['G']  = 'scrollToBottom';
  keyToCommandRegistry['<c-d>'] = "scrollPageDown";
  keyToCommandRegistry['<c-u>'] = "scrollPageUp";
  keyToCommandRegistry['r']  = 'reload';

  keyToCommandRegistry['ba'] = 'goBack';
  keyToCommandRegistry['H']  = 'goBack';<commit_msg>Keep tab queue per-window instead of globally. This mirrors behavior in both Vim and Vimperator. Closes #2.<commit_after><html>
<head>
<script type="text/javascript" charset="utf-8">
  var tabQueue = {}; // windowId -> Array
  var keyQueue = ""; // Queue of keys typed

  // Port handler mapping
  var portHandlers = { "keyDown":              handleKeyDown,
                       "returnScrollPosition": handleReturnScrollPosition };

  // Event handlers
  var selectionChangedHandlers = [];
  var getScrollPositionHandlers = {}; // tabId -> function (tab, scrollX, scrollY);
  var tabLoadedHandlers = {}; // tabId -> function ()

  chrome.extension.onConnect.addListener(function(port, name) {
    var senderTabId = port.sender.tab ? port.sender.tab.id : null;
    // If this is a tab we've been waiting to open, execute any "tab loaded" handlers, e.g. to restore
    // the tab's scroll position.
    if (senderTabId && tabLoadedHandlers[senderTabId]) {
      var toCall = tabLoadedHandlers[senderTabId];
      // Delete first to be sure there's no circular events.
      delete tabLoadedHandlers[senderTabId];
      toCall.call();
    }

    if (portHandlers[port.name])
      port.onMessage.addListener(portHandlers[port.name]);

  });

  function handleReturnScrollPosition(args) {
    if (getScrollPositionHandlers[args.currentTab.id]) {
      // Delete first to be sure there's no circular events.
      var toCall = getScrollPositionHandlers[args.currentTab.id];
      delete getScrollPositionHandlers[args.currentTab.id];
      toCall(args.currentTab, args.scrollX, args.scrollY);
    }
  }

  chrome.tabs.onSelectionChanged.addListener(function (tabId, selectionInfo) {
    if (selectionChangedHandlers.length > 0) { selectionChangedHandlers.pop().call(); }
  });

  function repeatFunction(func, totalCount, currentCount) {
    if (currentCount < totalCount)
      func(function () { repeatFunction(func, totalCount, currentCount + 1); });
  }

  // Returns the currently selected tab along with scroll coordinates. Pass in a callback of the form:
  //   function (tab, scrollX, scrollY) { .. }
  function getCurrentTabWithScrollPosition(callback) {
    chrome.tabs.getSelected(null, function(tab) {
      getScrollPositionHandlers[tab.id] = callback;
      var scrollPort = chrome.tabs.connect(tab.id, { name: "getScrollPosition" });
      scrollPort.postMessage({currentTab: tab});
    });
  }

  // Start action functions
  function createTab(callback) {
    chrome.tabs.create({}, function(tab) { callback(); });
  }

  function nextTab() { selectTab("next"); }
  function previousTab() { selectTab("previous"); }

  /*
   * Selects a tab before or after the currently selected tab. Direction is either "next" or "previous".
   */
  function selectTab(direction) {
    chrome.tabs.getAllInWindow(null, function(tabs) {
      if (tabs.length <= 1)
        return;
      for (var i = 0; i < tabs.length; i++) {
        if (tabs[i].selected) {
          var delta = (direction == "next") ? 1 : -1;
          var toSelect = tabs[(i + delta + tabs.length) % tabs.length];
          chrome.tabs.update(toSelect.id, { selected: true });
          break;
        }
      }
    });
  }

  function removeTab(callback) {
    getCurrentTabWithScrollPosition(function(tab, scrollX, scrollY) {
                                      var tabQueueEntry = { tabUrl: tab.url,
                                                            scrollX: scrollX,
                                                            scrollY: scrollY };

                                      if (tabQueue[tab.windowId])
                                        tabQueue[tab.windowId].push(tabQueueEntry);
                                      else
                                        tabQueue[tab.windowId] = [tabQueueEntry];

                                      chrome.tabs.remove(tab.id);
                                      // We can't just call the callback here because we actually need to wait
                                      // for the selection to change to consider this action done.
                                      selectionChangedHandlers.push(callback);
                                    });
  }

  function restoreTab(callback) {
    // TODO(ilya): Should this be getLastFocused instead?
    chrome.windows.getCurrent(function (window) {
      if (tabQueue[window.id] && tabQueue[window.id].length > 0)
      {
        var tabQueueEntry = tabQueue[window.id].pop();

        // Clean out the tabQueue so we don't have unused windows laying about.
        if (tabQueue[window.id].length == 0)
          delete tabQueue[window.id];

        // We have to chain a few callbacks to set the appropriate scroll position. We can't just wait until the
        // tab is created because the content script is not available during the "loading" state. We need to
        // wait until that's over before we can call setScrollPosition.
        chrome.tabs.create({ url: tabQueueEntry.tabUrl }, function(tab) {
          tabLoadedHandlers[tab.id] = function() {
            var scrollPort = chrome.tabs.connect(tab.id, {name: "setScrollPosition"});
            scrollPort.postMessage({ scrollX: tabQueueEntry.scrollX, scrollY: tabQueueEntry.scrollY });
          };

          callback();
        });
      }
    });
  }
  // End action functions

  var keyToCommandRegistry = {};
  keyToCommandRegistry['j']  = 'scrollDown';
  keyToCommandRegistry['k']  = 'scrollUp';
  keyToCommandRegistry['h']  = 'scrollLeft';
  keyToCommandRegistry['l']  = 'scrollRight';
  keyToCommandRegistry['gg'] = 'scrollToTop';
  keyToCommandRegistry['G']  = 'scrollToBottom';
  keyToCommandRegistry['<c-d>'] = "scrollPageDown";
  keyToCommandRegistry['<c-u>'] = "scrollPageUp";
  keyToCommandRegistry['r']  = 'reload';

  keyToCommandRegistry['ba'] = 'goBack';
  keyToCommandRegistry['H']  = 'goBack';
{'fraction_non_alphanumeric': 0.11835726698984725, 'fraction_numerical': 0.0016463916582822647, 'mean_word_length': 2.5615635179153093, 'pattern_counts': {'":': 4, '<': 19, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7516459', 'n_tokens_mistral': 3092, 'n_tokens_neox': 2970, 'n_words': 772}