Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 2cbb3171efcd336d435592e1f08c3642bf928b0f 0 parents
@k2nr authored
0  README
No changes.
12 background.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script type="text/javascript" src="utils.js"></script>
+<script type="text/javascript" src="keys.js"></script>
+<script type="text/javascript" src="commands.js"></script>
+<script type="text/javascript" src="settings.js"></script>
+<script type="text/javascript" src="vichrome.js"></script>
+<script>
+ init();
+</script>>
+</head>
+</html>
145 commands.js
@@ -0,0 +1,145 @@
+
+var commandTable = {
+ openNewTab : openNewTab
+ ,closeCurTab : closeCurTab
+ ,moveNextTab : moveNextTab
+ ,movePrevTab : movePrevTab
+ ,reloadTab : sendRequestToSelectedTab
+ ,scrollUp : sendRequestToSelectedTab
+ ,scrollDown : sendRequestToSelectedTab
+ ,scrollLeft : sendRequestToSelectedTab
+ ,scrollRight : sendRequestToSelectedTab
+ ,pageUp : sendRequestToSelectedTab
+ ,pageDown : sendRequestToSelectedTab
+ ,goTop : sendRequestToSelectedTab
+ ,goBottom : sendRequestToSelectedTab
+ ,backHist : sendRequestToSelectedTab
+ ,forwardHist : sendRequestToSelectedTab
+ ,escape : escape
+}
+
+var KeyQueue = function(){
+ this.a = "";
+ this.timerId = 0;
+ this.isWaiting = false;
+
+ this.queue = function(s) {
+ this.a += s;
+ return this;
+ }
+
+ this.reset = function() {
+ this.a = "";
+ this.stopTimer();
+ }
+
+ this.stopTimer = function() {
+ if( this.isWaiting ) {
+ Logger.d("stop timeout");
+ clearTimeout( this.timerId );
+ this.isWaiting = false;
+ }
+ }
+
+ this.startTimer = function( callback, ms ) {
+ if( this.isWaiting ) {
+ Logger.e("startTimer:timer already running");
+ } else {
+ Logger.d("commandTimer set");
+ this.isWaiting = true;
+ setTimeout( callback, ms );
+ }
+ }
+
+ // returns right key sequence.if right key sequence isn't built up, return null
+ this.getNextKeySequence = function() {
+ this.stopTimer();
+
+ if( SettingManager.isValidKeySeq(this.a) ) {
+ // keySeq has corresponding command
+ ret = this.a;
+ this.a = "";
+ return ret;
+ } else {
+ if( !SettingManager.isValidKeySeqAvailable(this.a) ) {
+ Logger.d("invalid key sequence:" + this.a);
+ this.a = "";
+ } else {
+ // possible key sequences are available.wait next key.
+ var thisObj = this;
+
+ this.startTimer( function() {
+ Logger.d("command wait timeout.reset key queue:" + thisObj.a);
+ thisObj.a = "";
+ thisObj.isWaiting = false;
+ }, SettingManager.get("commandWaitTimeOut") );
+ }
+ return null;
+ }
+ }
+};
+
+var keyQueue = new KeyQueue ();
+
+function executeCommand (com) {
+ commandTable[com](com);
+}
+
+function sendRequestToSelectedTab (com) {
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.sendRequest(tab.id, {command:com}, null);
+ });
+}
+
+function openNewTab () {
+ chrome.tabs.create({}, function(tab){ });
+}
+
+function closeCurTab () {
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.remove(tab.id, function(){
+ });
+ });
+}
+
+function moveNextTab () {
+ moveTab( 1 );
+}
+
+function movePrevTab () {
+ moveTab( -1 );
+}
+
+function escape () {
+ keyQueue.reset();
+ sendRequestToSelectedTab( "blur" );
+}
+
+function moveTab ( offset ) {
+ chrome.tabs.getAllInWindow( null, function( tabs ) {
+ var nTabs = tabs.length;
+ chrome.tabs.getSelected(null, function( tab ) {
+ var idx = tab.index + offset;
+ if( idx < 0 ) {
+ idx = nTabs - 1;
+ } else if( idx >= nTabs ) {
+ idx = 0;
+ }
+ chrome.tabs.update( tabs[idx].id, { selected:true }, function(){ });
+ });
+ });
+}
+
+function getCommand (s) {
+ if( s == "<ESC>" ) {
+ keyQueue.reset();
+ var keySeq = s;
+ } else {
+ keyQueue.queue(s);
+ var keySeq = keyQueue.getNextKeySequence();
+ }
+
+ var keyMap = SettingManager.get("keyMappings");
+ if( keyMap && keySeq )
+ return keyMap[keySeq];
+}
281 content_script.js
@@ -0,0 +1,281 @@
+var keyPort;
+var settingPort;
+var modeInsert;
+var modeSearch;
+var settings = [];
+
+var reqListeners = {
+ scrollUp : reqScrollUp
+ ,scrollDown : reqScrollDown
+ ,scrollLeft : reqScrollLeft
+ ,scrollRight : reqScrollRight
+ ,pageUp : reqPageUp
+ ,pageDown : reqPageDown
+ ,goTop : reqGoTop
+ ,goBottom : reqGoBottom
+ ,reloadTab : reqReloadTab
+ ,backHist : reqBackHist
+ ,forwardHist : reqForwardHist
+ ,blur : reqBlur
+}
+
+function reqScrollDown () {
+ window.scrollBy( 0, settings["scrollPixelCount"] );
+}
+
+function reqScrollUp () {
+ window.scrollBy( 0, -settings["scrollPixelCount"] );
+}
+
+function reqScrollLeft () {
+ window.scrollBy( -settings["scrollPixelCount"], 0 );
+}
+
+function reqScrollRight () {
+ window.scrollBy( settings["scrollPixelCount"], 0 );
+}
+
+function reqPageDown () {
+ window.scrollBy( 0, window.innerHeight / 2 );
+}
+
+function reqPageUp () {
+ window.scrollBy( 0, -window.innerHeight / 2 );
+}
+
+function reqGoTop () {
+ window.scrollTo( window.pageXOffset, 0 );
+}
+
+function reqGoBottom () {
+ window.scrollTo( window.pageXOffset, document.body.scrollHeight - window.innerHeight );
+}
+
+function reqBackHist () {
+ window.history.back();
+}
+
+function reqForwardHist () {
+ window.history.forward();
+}
+
+function reqBlur () {
+ document.activeElement.blur();
+}
+
+function reqReloadTab() {
+ window.location.reload();
+}
+
+function onBlur (e) {
+ Logger.d("onBlur");
+ exitInsertMode();
+}
+
+
+function onKeyDown (e) {
+ Logger.d("onKeyDown", e);
+
+ if( preHandleKeyEvent(e) ) {
+ postKeyMessage(e);
+ }
+}
+
+function onKeyPress (e) {
+ Logger.d( "onKeyPress", e );
+
+ if( preHandleKeyEvent(e) ) {
+ postKeyMessage(e);
+ }
+}
+
+function postKeyMessage (e) {
+ keyPort.postMessage({keyCode : e.keyCode,
+ charCode : e.charCode,
+ meta : e.metaKey,
+ alt : e.altKey,
+ ctrl : e.ctrlKey,
+ shift : e.shiftKey});
+}
+
+function isOnlyModifier (e) {
+ switch(e.keyCode) {
+ case keyCodes.Shift:
+ case keyCodes.Ctrl:
+ case keyCodes.Meta:
+ case keyCodes.Alt:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// decide whether to post the key event and do some pre-post process
+// return true if the key event can be posted.
+function preHandleKeyEvent (e) {
+ // vichrome doesn't handle meta and alt key for now
+ if( e.metaKey || e.altKey ) {
+ return false;
+ }
+ if( isOnlyModifier(e) ) {
+ return false;
+ }
+
+ // TODO:search mode
+
+ if(isInInsertMode()){
+ if(e.type == "keydown") {
+ if( KeyManager.isESC(e.keyCode, e.ctrlKey) ) {
+ return true;
+ } else if(keyCodes.F1 <= e.keyCode && e.keyCode <= keyCodes.F12){
+ return true;
+ } else if( e.ctrlKey ) {
+ return true;
+ }
+ } else {
+ // character key do not need to be handled in insert mode
+ return false;
+ }
+ } else {
+ if(e.type == "keypress") {
+ event.preventDefault();
+ event.stopPropagation();
+ return true;
+ } else if(e.type == "keydown") {
+ // some web sites set their own key bind(google instant search etc).
+ // to prevent messing up vichrome's key bind from them,
+ // we have to stop event propagation here.
+ // TODO:we should only stop when a valid(handlable) key event come.
+ event.stopPropagation();
+ if( e.ctrlKey ) {
+ // TODO:some keys cannot be recognized with keyCode e.g. C-@
+ return true;
+ }
+ if( KeyManager.isESC( e.keyCode, e.ctrlKey ) ) {
+ return true;
+ }
+ // keydown only catch key codes that are not passed to keypress
+ switch(e.keyCode) {
+ case keyCodes.Tab :
+ case keyCodes.BS :
+ case keyCodes.DEL :
+ case keyCodes.Left :
+ case keyCodes.Up :
+ case keyCodes.Right :
+ case keyCodes.Down :
+ case keyCodes.F1 :
+ case keyCodes.F2 :
+ case keyCodes.F3 :
+ case keyCodes.F4 :
+ case keyCodes.F5 :
+ case keyCodes.F6 :
+ case keyCodes.F7 :
+ case keyCodes.F8 :
+ case keyCodes.F9 :
+ case keyCodes.F10 :
+ case keyCodes.F11 :
+ case keyCodes.F12 :
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
+
+
+function onFocus (e) {
+ if( isEditable(e.target) ) {
+ enterInsertMode();
+ } else {
+ exitInsertMode();
+ }
+ Logger.d("onFocus:insertMode=" + isInInsertMode() );
+}
+
+function enterInsertMode () {
+ modeInsert = true;
+}
+
+function exitInsertMode () {
+ modeInsert = false;
+}
+
+function isInInsertMode () {
+ return modeInsert;
+}
+
+function isInSearchMode () {
+ return modeSearch;
+}
+
+function isEditable (target) {
+ if (target.isContentEditable) {
+ return true;
+ }
+
+ if(target.nodeName=="INPUT" && target.type == "text") {
+ return true;
+ }
+
+ ignoreList = ["TEXTAREA"];
+ if(ignoreList.indexOf(target.nodeName) >= 0){
+ return true;
+ }
+
+ return false;
+}
+
+function onSettingUpdated (msg) {
+ if(msg.name == "all") {
+ settings = msg.value;
+ } else {
+ settings[msg.name] = msg.value;
+ }
+}
+
+function setupPorts() {
+ keyPort = chrome.extension.connect({ name : "key" });
+
+ settingPort = chrome.extension.connect({ name : "reqSettings" });
+ settingPort.onMessage.addListener( onSettingUpdated );
+ settingPort.postMessage({ name : "all" });
+}
+
+function addWindowListeners() {
+ window.addEventListener("keydown" , onKeyDown , true);
+ window.addEventListener("keypress" , onKeyPress , true);
+ window.addEventListener("focus" , onFocus , true);
+ window.addEventListener("blur" , onBlur , true);
+}
+
+function addRequestListener() {
+ chrome.extension.onRequest.addListener(function(req, sender, sendResponse) {
+ if(reqListeners[req.command]) {
+ reqListeners[req.command]();
+ }
+
+ sendResponse();
+ });
+}
+
+function onEnabled() {
+ init();
+}
+
+function init () {
+ setupPorts();
+ addRequestListener();
+ addWindowListeners();
+
+ // should evaluate focused element on initialization.
+ if( isEditable( document.activeElement ) ) {
+ enterInsertMode();
+ }
+}
+
+window.addEventListener("DOMContentLoaded", function() {
+ // DELTEME: onEnable should be triggered from background page.
+ onEnabled();
+});
+
87 keys.js
@@ -0,0 +1,87 @@
+var KeyManager = {
+ isESC : function (keyCode, ctrl) {
+ if( keyCode == keyCodes.ESC ) {
+ return true;
+ } else if( ctrl && keyCode == '[' ) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ convertKeyCodeToStr : function (msg) {
+ key = "";
+ if(65 <= msg.keyCode && msg.keyCode <= 90){
+ if(msg.shift) {
+ key = String.fromCharCode(msg.keyCode);
+ } else {
+ key = String.fromCharCode(msg.keyCode + 32);
+ }
+ if(msg.ctrl) {
+ key = "<C-" + key + ">";
+ }
+
+ return key;
+ } else if(31 <= msg.keyCode && msg.keyCode <= 126){
+ return String.fromCharCode(msg.keyCode);
+ }
+
+ switch(msg.keyCode) {
+ case keyCodes.Tab : return "<TAB>";
+ case keyCodes.BS : return "<BS>";
+ case keyCodes.DEL : return "<DEL>";
+ case keyCodes.CR : return "<CR>";
+ case keyCodes.SP : return "<SP>";
+ case keyCodes.Left : return "<Left>";
+ case keyCodes.Up : return "<Up>";
+ case keyCodes.Right : return "<Right>";
+ case keyCodes.Down : return "<Down>";
+ case keyCodes.F1 : return "<F1>";
+ case keyCodes.F2 : return "<F2>";
+ case keyCodes.F3 : return "<F3>";
+ case keyCodes.F4 : return "<F4>";
+ case keyCodes.F5 : return "<F5>";
+ case keyCodes.F6 : return "<F6>";
+ case keyCodes.F7 : return "<F7>";
+ case keyCodes.F8 : return "<F8>";
+ case keyCodes.F9 : return "<F9>";
+ case keyCodes.F10 : return "<F10>";
+ case keyCodes.F11 : return "<F11>";
+ case keyCodes.F12 : return "<F12>";
+ }
+
+ if(KeyManager.isESC(msg.keyCode, msg.ctrl)) {
+ return "<ESC>";
+ }
+ }
+};
+
+var keyCodes = {
+ ESC : 27
+ ,Tab : 9
+ ,Shift : 16
+ ,BS : 8
+ ,Alt : 18
+ ,Ctrl : 17
+ ,Meta : 91
+ ,DEL : 46
+ ,CR : 13
+ ,SP : 32
+ ,Left : 37
+ ,Up : 38
+ ,Right : 39
+ ,Down : 40
+ ,F1 : 112
+ ,F2 : 113
+ ,F3 : 114
+ ,F4 : 115
+ ,F5 : 116
+ ,F6 : 117
+ ,F7 : 118
+ ,F8 : 119
+ ,F9 : 120
+ ,F10 : 121
+ ,F11 : 122
+ ,F12 : 123
+};
+
28 manifest.json
@@ -0,0 +1,28 @@
+{
+ "name": "vichrome",
+ "version": "0.1",
+ "description": "vi like key bind for chrome",
+ "browser_action": {
+ "default_icon": "icon.png"
+ },
+ "background_page": "background.html",
+ "options_page": "options.html",
+ "permissions": [
+ "tabs",
+ "bookmarks",
+ "http://*/*",
+ "https://*/*"
+ ],
+ "content_scripts": [
+ {
+ "matches": ["<all_urls>"],
+ "js": [
+ "keys.js",
+ "utils.js",
+ "content_script.js"
+ ],
+ "run_at": "document_start",
+ "all_frames": true
+ }
+ ]
+}
103 settings.js
@@ -0,0 +1,103 @@
+var SettingManager = {
+ defaultSettings : {
+ "scrollPixelCount" : 40,
+ "searchEngine" : "http://www.google.com/",
+ "commandWaitTimeOut" : 2000,
+ "keyMappings" : {
+ j : "scrollDown"
+ ,k : "scrollUp"
+ ,h : "scrollLeft"
+ ,l : "scrollRight"
+ ,d : "pageDown"
+ ,u : "pageUp"
+ ,gg : "goTop"
+ ,G : "goBottom"
+ ,t : "openNewTab"
+ ,x : "closeCurTab"
+ ,">" : "moveNextTab"
+ ,"<" : "movePrevTab"
+ ,r : "reloadTab"
+ ,H : "backHist"
+ ,L : "forwardHist"
+ ,"<ESC>" : "escape"
+ },
+ },
+
+ settingNames : [
+ "scrollPixelCount",
+ "searchEngine",
+ "commandWaitTimeOut",
+ "keyMappings",
+ ],
+
+ availableKeySeq : [
+ "j"
+ ,"k"
+ ,"h"
+ ,"l"
+ ,"d"
+ ,"u"
+ ,"gg"
+ ,"G"
+ ,"k"
+ ,"t"
+ ,"x"
+ ,">"
+ ,"<"
+ ,"r"
+ ,"H"
+ ,"L"
+ ,"<ESC>"
+ ],
+
+ associateKeyMap : {},
+
+ getAll : function() {
+ settings = {};
+ for (var i=0; i < this.settingNames.length; i++) {
+ if ( localStorage[this.settingNames[i]] ) {
+ settings[this.settingNames[i]] = localStorage[this.settingNames[i]];
+ } else {
+ settings[this.settingNames[i]] = this.defaultSettings[this.settingNames[i]];
+ }
+ };
+
+ return settings;
+ },
+
+ get : function(name) {
+ if ( localStorage[name] ) {
+ return localStorage[name];
+ } else {
+ return this.defaultSettings[name];
+ }
+ },
+
+ set : function(name, value) {
+ localStorage[name] = value;
+
+ if(this.setCb)
+ this.setCb(name, value);
+ },
+
+ setCb : null,
+
+ isValidKeySeq : function(keySeq) {
+ if( this.availableKeySeq.indexOf( keySeq ) >= 0 ) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ isValidKeySeqAvailable : function(keySeq) {
+ regexp = new RegExp( "^" + keySeq );
+ for (var i=0; i < this.availableKeySeq.length; i++) {
+ if( regexp.test( this.availableKeySeq[i] ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+};
39 utils.js
@@ -0,0 +1,39 @@
+
+var Logger = {
+ DEBUG : 1,
+ WARNING : 2,
+ ERROR : 3,
+ FATAL : 4,
+ NONE : 5,
+
+ __log : function(a, o) {
+ if(o)
+ console.log( "vichrome:" + a + " :%o", o );
+ else
+ console.log( "vichrome:" + a );
+ },
+
+ d : function(a, o) {
+ if(VICHROME_LOG_LEVEL >= Logger.DEBUG)
+ this.__log(a, o);
+ },
+
+ w : function(a, o) {
+ if(VICHROME_LOG_LEVEL >= Logger.WARNING)
+ this.__log(a, o);
+ },
+
+ e : function(a, o) {
+ if(VICHROME_LOG_LEVEL >= Logger.ERROR)
+ this.__log(a, o);
+ },
+
+ f : function(a, o) {
+ if(VICHROME_LOG_LEVEL >= Logger.FATAL)
+ this.__log(a, o);
+ }
+
+};
+
+VICHROME_LOG_LEVEL = Logger.DEBUG;
+
32 vichrome.js
@@ -0,0 +1,32 @@
+var portListeners = {
+ key : onKeyDown
+ ,reqSettings : reqGetSettings
+}
+
+function onKeyDown (msg) {
+ var s = KeyManager.convertKeyCodeToStr(msg);
+ com = getCommand( s );
+ Logger.d( "onKeyDown: " + s + " : " + com );
+ if( com ) {
+ executeCommand( com );
+ }
+}
+
+function reqGetSettings (msg, port) {
+ sendMsg = {name : msg.name};
+
+ if(msg.name == "all") {
+ sendMsg["value"] = SettingManager.getAll();
+ } else {
+ sendMsg["value"] = SettingManager.get(msg.name);
+ }
+
+ port.postMessage( sendMsg );
+}
+
+function init () {
+ chrome.extension.onConnect.addListener(function(port) {
+ port.onMessage.addListener( portListeners[port.name] );
+ });
+}
+
Please sign in to comment.
Something went wrong with that request. Please try again.