├── .gitignore ├── .gitmodules ├── BUILD-AND-TEST.md ├── LICENSE ├── Makefile ├── NOTICE ├── README-BIN-FILES.md ├── README-ja.md ├── README.md ├── changelog.txt ├── dist ├── chrome.xml ├── firefox.json ├── firefox.rdf ├── opera-blink.xml ├── update.rdf ├── wasavi.crx ├── wasavi.nex └── wasavi.xpi ├── distribute-scripts ├── package.json ├── signxpi ├── src ├── chrome │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── _locales │ │ ├── core.json │ │ ├── en_US │ │ │ └── messages.json │ │ ├── ja │ │ │ └── messages.json │ │ ├── locales.json │ │ └── plural_rule.json │ ├── backend │ │ ├── lib │ │ │ ├── ContextMenu.js │ │ │ ├── Memorandum.js │ │ │ ├── RuntimeOverwriteSettings.js │ │ │ ├── SimilarityComputer.js │ │ │ ├── SyncStorage.js │ │ │ ├── init.js │ │ │ ├── main.js │ │ │ └── marked.js │ │ └── main.html │ ├── consumer_keys.json.template │ ├── frontend │ │ ├── agent.js │ │ ├── classes.js │ │ ├── classes_ex.js │ │ ├── classes_search.js │ │ ├── classes_subst.js │ │ ├── classes_ui.js │ │ ├── classes_undo.js │ │ ├── extension_wrapper.js │ │ ├── init.js │ │ ├── qeema.js │ │ ├── unicode_utils.js │ │ ├── unistring.js │ │ ├── utils.js │ │ └── wasavi.js │ ├── images │ │ ├── appsweets.png │ │ ├── banner.xcf │ │ ├── icon016.png │ │ ├── icon048.png │ │ └── icon128.png │ ├── manifest.json │ ├── options.html │ ├── scripts │ │ ├── options-core.js │ │ └── page_context.js │ ├── sounds │ │ ├── beep.mp3 │ │ ├── beep.mp3.txt │ │ ├── beep.ogg │ │ ├── beep.ogg.txt │ │ ├── launch.mp3 │ │ ├── launch.mp3.txt │ │ ├── launch.ogg │ │ └── launch.ogg.txt │ ├── styles │ │ └── wasavi.css │ ├── unicode │ │ ├── fftt_general.dat │ │ ├── fftt_han_ja.dat │ │ └── linebreak.dat │ └── wasavi.html ├── firefox ├── mediators │ ├── keysnail │ │ └── wasavi_mediator.ks.js │ └── vimperator │ │ └── wasavi_mediator.js ├── opera-blink ├── template-chrome.xml ├── template-firefox.json ├── template-opera-blink.xml ├── unicode-tools │ ├── make-fftt-general-dict.js │ ├── make-fftt-hanja-dict.js │ ├── make-linebreak-dict.js │ ├── make-prop-regex.js │ ├── make-scripts.js │ └── utils.js ├── unit-tests │ ├── IncDec.js │ ├── MapManager.js │ ├── RegexConverter.js │ ├── SortWorker.js │ ├── expr.js │ ├── splitex.js │ ├── strftime.js │ ├── toVisibleString.js │ └── unicode_linebreaker.js └── wd-tests │ ├── almost-min.js │ ├── app-mode.js │ ├── bound.js │ ├── editing.js │ ├── ex.js │ ├── filesystem-test-files │ ├── hello-wasavi.txt │ └── wasavi-test │ │ ├── read test.txt │ │ └── write test.txt │ ├── filesystem.js │ ├── index.js │ ├── insertion.js │ ├── launch-and-quit.js │ ├── learning-the-vi-editor-6th.js │ ├── line-input.js │ ├── motion.js │ ├── op-change.js │ ├── op-delete.js │ ├── op-shift.js │ ├── op-yank.js │ ├── range-symbol.js │ ├── scroll.js │ ├── server │ ├── surround.js │ └── undo.js └── www ├── .htaccess ├── authorized.html ├── favicon.ico ├── icon016.png ├── icon048.png ├── icon128.png ├── index.html ├── manifest.json ├── test_frame.html └── wasavi.appcache /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | *~ 3 | Thumbs.db 4 | desktop.ini 5 | Session.vim 6 | 7 | consumer_keys.json 8 | consumer_keys.bin 9 | wasavi.pem 10 | app.mk 11 | .rsync* 12 | mkmain 13 | mkqeema 14 | pushbins 15 | resetbins 16 | syncsrc 17 | syncwww 18 | .embryo 19 | node_modules 20 | src/chrome/backend/oldlib/ 21 | src/wd-tests/dst/ 22 | src/wd-tests/lib/ 23 | src/wd-tests/profile/ 24 | src/wd-tests/result.txt 25 | src/wd-tests/config.xml 26 | src/wd-tests/.stop-server-result 27 | src/unicode-tools/ucd/ 28 | src/unicode-tools/unihan/ 29 | dist/wasavi_chrome_web_store.zip 30 | test.log 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/chrome/backend/lib/kosian"] 2 | path = src/chrome/backend/lib/kosian 3 | url = git@github.com:akahuku/kosian.git 4 | -------------------------------------------------------------------------------- /BUILD-AND-TEST.md: -------------------------------------------------------------------------------- 1 | wasavi building and testing guide 2 | ================================= 3 | 4 | 1. Preparation 5 | 6 | The following objects are required to build and test wasavi: 7 | 8 | * OS which supports a symbolic link native 9 | * node.js and npm (to manage packages) 10 | * web-ext, via npm (to run wasavi on Firefox) 11 | * mocha, via npm (to test wasavi) 12 | * Selenium javascript binding (to test wasavi) 13 | * php (to re-build unicode data files) 14 | * make 15 | * gcc 16 | * Information of your AMO account (to build and sign wasavi.xpi) 17 | create ~/.amo-account.ini and describe the account info in the following format: 18 | ``` 19 | AMO_API_KEY= 20 | AMO_API_SECRET= 21 | ``` 22 | * Information of your Dropbox, GoogleDrive, OneDrive accounts (to use filesystem functionality) 23 | copy `src/chrome/consumer_keys.json.template` to `src/chrome/consumer_keys.json` and edit it 24 | 25 | 2. How to set up the source code 26 | 27 | ``` 28 | $ git clone git@github.com:akahuku/wasavi.git 29 | $ cd wasavi 30 | $ git submodule update -i 31 | $ npm install 32 | ``` 33 | 34 | 3. How to run wasavi on Chrome (and Opera) 35 | 36 | * Start Chrome with special profile: 37 | ``` 38 | $ make run-chrome 39 | ``` 40 | * A special profile is placed `src/wd-tests/profile/chrome`. 41 | * If it is first run, navigate to `chrome://extensions`, push `Load unpacked extension...`, then set `src/chrome` directory. 42 | * If you want to build your own wasavi.crx and wasavi.nex, make a wasavi.pem file at `chrome://extensions` page and place it to repository root. 43 | 44 | 4. How to run wasavi on Firefox 45 | 46 | * Start Firefox: 47 | ``` 48 | $ make debug-firefox 49 | ``` 50 | 51 | 5. How to build 52 | 53 | ``` 54 | $ make 55 | ``` 56 | 57 | 6. How to functional test with Selenium 58 | 59 | Copy `src/wd-tests/filesystem-test-files/*` to each root directory of Dropbox, Google Drive, OneDrive. 60 | 61 | * /hello-wasavi.txt (used for testing file name completion, so content is optional) 62 | * /wasavi-test/read test.txt (content: 'hello,\nworld') 63 | * /wasavi-test/write test.txt (content is dynamically created during testing) 64 | 65 | ``` 66 | $ make test-chrome 67 | ``` 68 | ``` 69 | $ make test-opera 70 | ``` 71 | ``` 72 | $ make test-firefox 73 | ``` 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2015 akahuku, akahuku@gmail.com 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # application macros 2 | # ======================================== 3 | 4 | VERSION := $(shell echo -n `git describe --abbrev=0|awk 'match($$0,/([0-9]+\.[0-9]+)/,a){print a[0]}'`.`git rev-list --count HEAD`) 5 | 6 | SHELL := /bin/sh 7 | PATH_SEPARATOR := / 8 | 9 | CHROME := google-chrome 10 | BLINKOPERA := opera 11 | FIREFOX := firefox 12 | CYGPATH := echo 13 | REALPATH := realpath 14 | 15 | ZIP := zip -qr9 16 | UNZIP := unzip 17 | 18 | RSYNC := rsync 19 | 20 | # basic macros 21 | # ======================================== 22 | 23 | PRODUCT = wasavi 24 | DIST_DIR = dist 25 | SRC_DIR = src 26 | EMBRYO_DIR = .embryo 27 | TOOL_DIR = node_modules/brisket 28 | 29 | TEST_WWW_SERVER = $(SRC_DIR)/wd-tests/server 30 | TEST_FRAME_URL = http://127.0.0.1:8888/test_frame.html 31 | TEST_SHUTDOWN_URL = http://127.0.0.1:8888/shutdown 32 | TEST_MOCHA_OPTS = --timeout=10000 \ 33 | --reporter=$(SRC_DIR)/wd-tests/almost-min.js \ 34 | $(SRC_DIR)/wd-tests/index.js 2>&1 | tee test.log 35 | 36 | RSYNC_OPT = -rptL --delete \ 37 | --exclude '*.sw?' --exclude '*.bak' --exclude '*~' --exclude '*.sh' \ 38 | --exclude 'banner*.xcf' --exclude 'banner*.png' \ 39 | --exclude '.*' \ 40 | --exclude '$(CRYPT_SRC_FILE)*' \ 41 | --exclude 'FirefoxImpl.js' --exclude 'OperaImpl.js' 42 | 43 | CRYPT_KEY_FILE = LICENSE 44 | CRYPT_SRC_FILE = consumer_keys.json 45 | CRYPT_DST_FILE = consumer_keys.bin 46 | 47 | CHROME_SUFFIX = crx 48 | CHROME_SRC_DIR = chrome 49 | CHROME_EXT_ID = dgogifpkoilgiofhhhodbodcfgomelhe 50 | CHROME_EXT_LOCATION = https://github.com/akahuku/$(PRODUCT)/raw/master/dist/$(PRODUCT).crx 51 | CHROME_UPDATE_LOCATION = https://github.com/akahuku/$(PRODUCT)/raw/master/dist/chrome.xml 52 | 53 | BLINKOPERA_SUFFIX = nex 54 | BLINKOPERA_SRC_DIR = opera-blink 55 | BLINKOPERA_EXT_ID = dgogifpkoilgiofhhhodbodcfgomelhe 56 | BLINKOPERA_EXT_LOCATION = https://github.com/akahuku/$(PRODUCT)/raw/master/dist/$(PRODUCT).nex 57 | BLINKOPERA_UPDATE_LOCATION = https://github.com/akahuku/$(PRODUCT)/raw/master/dist/opera-blink.xml 58 | 59 | FIREFOX_SUFFIX = xpi 60 | FIREFOX_SRC_DIR = firefox 61 | FIREFOX_EXT_ID = jid1-bmMwuNrx3u5hqQ@jetpack 62 | FIREFOX_EXT_LOCATION = https://github.com/akahuku/$(PRODUCT)/raw/master/dist/$(PRODUCT).xpi 63 | FIREFOX_UPDATE_LOCATION = https://github.com/akahuku/$(PRODUCT)/raw/master/dist/firefox.json 64 | 65 | # derived macros 66 | # ======================================== 67 | 68 | BINKEYS_PATH = $(CHROME_SRC_PATH)/$(CRYPT_DST_FILE) 69 | 70 | CHROME_TARGET_PATH = $(DIST_DIR)/$(PRODUCT).$(CHROME_SUFFIX) 71 | CHROME_MTIME_PATH = $(EMBRYO_DIR)/.$(CHROME_SUFFIX) 72 | CHROME_SRC_PATH = $(SRC_DIR)/$(CHROME_SRC_DIR) 73 | CHROME_EMBRYO_SRC_PATH = $(EMBRYO_DIR)/$(CHROME_SRC_DIR) 74 | CHROME_TEST_PROFILE_PATH = $(shell $(CYGPATH) $(SRC_DIR)/wd-tests/profile/chrome) 75 | 76 | BLINKOPERA_TARGET_PATH = $(DIST_DIR)/$(PRODUCT).$(BLINKOPERA_SUFFIX) 77 | BLINKOPERA_MTIME_PATH = $(EMBRYO_DIR)/.$(BLINKOPERA_SUFFIX) 78 | BLINKOPERA_SRC_PATH = $(SRC_DIR)/$(BLINKOPERA_SRC_DIR) 79 | BLINKOPERA_EMBRYO_SRC_PATH = $(EMBRYO_DIR)/$(BLINKOPERA_SRC_DIR) 80 | BLINKOPERA_TEST_PROFILE_PATH = $(shell $(CYGPATH) $(SRC_DIR)/wd-tests/profile/opera) 81 | 82 | FIREFOX_TARGET_PATH = $(DIST_DIR)/$(PRODUCT).$(FIREFOX_SUFFIX) 83 | FIREFOX_MTIME_PATH = $(EMBRYO_DIR)/.$(FIREFOX_SUFFIX) 84 | FIREFOX_SRC_PATH = $(SRC_DIR)/$(FIREFOX_SRC_DIR) 85 | FIREFOX_EMBRYO_SRC_PATH = $(EMBRYO_DIR)/$(FIREFOX_SRC_DIR) 86 | FIREFOX_TEST_PROFILE_PATH = $(shell $(CYGPATH) $(SRC_DIR)/wd-tests/profile/firefox) 87 | 88 | # local override of macros 89 | # ======================================== 90 | 91 | -include app.mk 92 | 93 | 94 | 95 | # basic rules 96 | # ======================================== 97 | 98 | all: crx nex xpi 99 | 100 | crx: $(CHROME_TARGET_PATH) 101 | 102 | nex: $(BLINKOPERA_TARGET_PATH) 103 | 104 | xpi: $(FIREFOX_TARGET_PATH) 105 | 106 | clean: 107 | rm -rf ./$(EMBRYO_DIR) 108 | 109 | $(BINKEYS_PATH): $(CHROME_SRC_PATH)/$(CRYPT_KEY_FILE) $(CHROME_SRC_PATH)/$(CRYPT_SRC_FILE) 110 | $(TOOL_DIR)/make-binkey.js \ 111 | --key $(CHROME_SRC_PATH)/$(CRYPT_KEY_FILE) \ 112 | --src $(CHROME_SRC_PATH)/$(CRYPT_SRC_FILE) \ 113 | --dst $@ 114 | 115 | FORCE: 116 | 117 | .PHONY: all crx nex xpi \ 118 | clean message \ 119 | test-chrome test-opera test-firefox \ 120 | run-chrome run-opera run-firefox debug-firefox \ 121 | start-server version \ 122 | FORCE 123 | 124 | 125 | 126 | # 127 | # rules to make wasavi.crx 128 | # ======================================== 129 | # 130 | 131 | # wasavi.crx 132 | $(CHROME_TARGET_PATH): $(CHROME_MTIME_PATH) $(BINKEYS_PATH) 133 | # copy all of sources to embryo dir 134 | $(RSYNC) $(RSYNC_OPT) --exclude 'wasavi_frame_noscript.html' \ 135 | $(CHROME_SRC_PATH)/ $(CHROME_EMBRYO_SRC_PATH) 136 | 137 | # update manifest 138 | $(TOOL_DIR)/update-chrome-manifest.js \ 139 | --indir $(CHROME_SRC_PATH) \ 140 | --outdir $(CHROME_EMBRYO_SRC_PATH) \ 141 | --ver $(VERSION) \ 142 | --strip-applications 143 | 144 | # build general crx 145 | [ -f $(PRODUCT).pem ] && $(CHROME) \ 146 | --lang=en \ 147 | --pack-extension=$(CHROME_EMBRYO_SRC_PATH) \ 148 | --pack-extension-key=$(PRODUCT).pem 149 | 150 | mv $(EMBRYO_DIR)/$(CHROME_SRC_DIR).$(CHROME_SUFFIX) $@ 151 | 152 | # update manifest for google web store 153 | $(TOOL_DIR)/update-chrome-manifest.js \ 154 | --indir $(CHROME_SRC_PATH) \ 155 | --outdir $(CHROME_EMBRYO_SRC_PATH) \ 156 | --ver $(VERSION) \ 157 | --strip-update-url \ 158 | --strip-applications 159 | 160 | # build zip archive for google web store 161 | rm -f $(DIST_DIR)/$(PRODUCT)_chrome_web_store.zip 162 | cd $(CHROME_EMBRYO_SRC_PATH) \ 163 | && find . -type f -print0 | sort -z | xargs -0 $(ZIP) \ 164 | ../../$(DIST_DIR)/$(PRODUCT)_chrome_web_store.zip 165 | 166 | # create update description file 167 | sed -e 's/@appid@/$(CHROME_EXT_ID)/g' \ 168 | -e 's!@location@!$(CHROME_EXT_LOCATION)!g' \ 169 | -e 's/@version@/$(VERSION)/g' \ 170 | $(SRC_DIR)/template-chrome.xml > $(DIST_DIR)/$(notdir $(CHROME_UPDATE_LOCATION)) 171 | 172 | @echo /// 173 | @echo /// created: $@, version $(VERSION) 174 | @echo /// 175 | 176 | # last mtime holder 177 | $(CHROME_MTIME_PATH): FORCE 178 | @mkdir -p $(CHROME_EMBRYO_SRC_PATH) $(DIST_DIR) 179 | $(TOOL_DIR)/mtime.js --dir $(CHROME_SRC_PATH) --base $(CHROME_TARGET_PATH) --out $@ 180 | 181 | 182 | 183 | # 184 | # rules to make wasavi.nex 185 | # ======================================== 186 | # 187 | 188 | # wasavi.nex 189 | $(BLINKOPERA_TARGET_PATH): $(BLINKOPERA_MTIME_PATH) $(BINKEYS_PATH) 190 | # copy all of sources to embryo dir 191 | $(RSYNC) $(RSYNC_OPT) --exclude='wasavi_frame_noscript.html' \ 192 | $(BLINKOPERA_SRC_PATH)/ $(BLINKOPERA_EMBRYO_SRC_PATH) 193 | 194 | # update manifest 195 | $(TOOL_DIR)/update-chrome-manifest.js \ 196 | --indir $(BLINKOPERA_SRC_PATH) \ 197 | --outdir $(BLINKOPERA_EMBRYO_SRC_PATH) \ 198 | --ver $(VERSION) \ 199 | --update-url $(BLINKOPERA_UPDATE_LOCATION) \ 200 | --strip-applications 201 | 202 | # build nex 203 | $(CHROME) \ 204 | --lang=en \ 205 | --pack-extension=$(BLINKOPERA_EMBRYO_SRC_PATH) \ 206 | --pack-extension-key=$(PRODUCT).pem 207 | 208 | mv $(EMBRYO_DIR)/$(BLINKOPERA_SRC_DIR).$(CHROME_SUFFIX) $@ 209 | 210 | # create update description file 211 | sed -e 's/@appid@/$(BLINKOPERA_EXT_ID)/g' \ 212 | -e 's!@location@!$(BLINKOPERA_EXT_LOCATION)!g' \ 213 | -e 's/@version@/$(VERSION)/g' \ 214 | $(SRC_DIR)/template-opera-blink.xml > $(DIST_DIR)/$(notdir $(BLINKOPERA_UPDATE_LOCATION)) 215 | 216 | @echo /// 217 | @echo /// created: $@, version $(VERSION) 218 | @echo /// 219 | 220 | # last mtime holder 221 | $(BLINKOPERA_MTIME_PATH): FORCE 222 | @mkdir -p $(BLINKOPERA_EMBRYO_SRC_PATH) $(DIST_DIR) 223 | $(TOOL_DIR)/mtime.js --dir $(BLINKOPERA_SRC_PATH) --base $(BLINKOPERA_TARGET_PATH) --out $@ 224 | 225 | 226 | 227 | # 228 | # rules to make wasavi.xpi 229 | # ======================================== 230 | # 231 | 232 | # wasavi.xpi 233 | $(FIREFOX_TARGET_PATH): $(FIREFOX_MTIME_PATH) $(BINKEYS_PATH) 234 | # copy all of sources to embryo dir 235 | $(RSYNC) $(RSYNC_OPT) \ 236 | $(FIREFOX_SRC_PATH)/ $(FIREFOX_EMBRYO_SRC_PATH) 237 | 238 | # update manifest 239 | $(TOOL_DIR)/update-chrome-manifest.js \ 240 | --indir $(FIREFOX_SRC_PATH) \ 241 | --outdir $(FIREFOX_EMBRYO_SRC_PATH) \ 242 | --ver $(VERSION) \ 243 | --strip-update-url 244 | 245 | # build and sign xpi 246 | ./signxpi \ 247 | -s $(FIREFOX_EMBRYO_SRC_PATH) \ 248 | -d $(DIST_DIR) 249 | 250 | # create update description file 251 | sed -e 's/@appid@/$(FIREFOX_EXT_ID)/g' \ 252 | -e 's!@location@!$(FIREFOX_EXT_LOCATION)!g' \ 253 | -e 's/@version@/$(VERSION)/g' \ 254 | $(SRC_DIR)/template-firefox.json > $(DIST_DIR)/$(notdir $(FIREFOX_UPDATE_LOCATION)) 255 | 256 | @echo /// 257 | @echo /// created: $@, version $(VERSION) 258 | @echo /// 259 | 260 | # last mtime holder 261 | $(FIREFOX_MTIME_PATH): FORCE 262 | @mkdir -p $(FIREFOX_EMBRYO_SRC_PATH) $(DIST_DIR) 263 | $(TOOL_DIR)/mtime.js --dir $(FIREFOX_SRC_PATH) --base $(FIREFOX_TARGET_PATH) --out $@ 264 | 265 | 266 | 267 | # 268 | # rules to make binary formed consumer keys 269 | # ======================================== 270 | # 271 | 272 | binkeys: $(BINKEYS_PATH) 273 | 274 | 275 | 276 | # 277 | # rules to make messages 278 | # ======================================== 279 | # 280 | 281 | message: FORCE 282 | # update locales.json 283 | $(TOOL_DIR)/update-locales.js \ 284 | --indir $(CHROME_SRC_PATH)/_locales 285 | 286 | # get diff of messages other than en-US 287 | $(TOOL_DIR)/make-messages.js \ 288 | --indir=$(CHROME_SRC_PATH) \ 289 | $(CHROME_SRC_PATH)/frontend/*.js \ 290 | $(CHROME_SRC_PATH)/backend/*.js \ 291 | $(CHROME_SRC_PATH)/backend/lib/kosian/*.js 292 | 293 | 294 | 295 | # 296 | # rules to test 297 | # ======================================== 298 | # 299 | 300 | test-chrome: FORCE 301 | @NODE_TARGET_BROWSER=chrome \ 302 | LANGUAGE=en \ 303 | mocha $(TEST_MOCHA_OPTS) 304 | 305 | test-opera: FORCE 306 | @NODE_TARGET_BROWSER=opera \ 307 | LANGUAGE=en \ 308 | mocha $(TEST_MOCHA_OPTS) 309 | 310 | test-firefox: FORCE 311 | @NODE_TARGET_BROWSER=firefox \ 312 | LANG=en \ 313 | mocha $(TEST_MOCHA_OPTS) 314 | 315 | run-chrome: FORCE 316 | node $(TEST_WWW_SERVER) & 317 | -mkdir -p $(CHROME_TEST_PROFILE_PATH) 318 | LANGUAGE=en $(CHROME) \ 319 | --start-maximized \ 320 | --lang=en \ 321 | --user-data-dir="$(CHROME_TEST_PROFILE_PATH)" \ 322 | $(TEST_FRAME_URL) 323 | wget -q -O - $(TEST_SHUTDOWN_URL) 324 | 325 | run-opera: FORCE 326 | node $(TEST_WWW_SERVER) & 327 | -mkdir -p $(BLINKOPERA_TEST_PROFILE_PATH) 328 | LANGUAGE=en $(BLINKOPERA) \ 329 | --start-maximized \ 330 | --lang=en \ 331 | --user-data-dir="$(BLINKOPERA_TEST_PROFILE_PATH)" \ 332 | $(TEST_FRAME_URL) 333 | wget -q -O - $(TEST_SHUTDOWN_URL) 334 | 335 | run-firefox: FORCE 336 | node $(TEST_WWW_SERVER) & 337 | -mkdir -p $(FIREFOX_TEST_PROFILE_PATH) 338 | LANG=en $(FIREFOX) \ 339 | -profile "$(FIREFOX_TEST_PROFILE_PATH)" \ 340 | $(TEST_FRAME_URL) 341 | wget -q -O - $(TEST_SHUTDOWN_URL) 342 | 343 | debug-firefox: FORCE 344 | node $(TEST_WWW_SERVER) & 345 | -mkdir -p $(FIREFOX_TEST_PROFILE_PATH)-debug 346 | web-ext run \ 347 | -u $(TEST_FRAME_URL) \ 348 | -s $(FIREFOX_SRC_PATH) \ 349 | -p $(FIREFOX_TEST_PROFILE_PATH)-debug \ 350 | --keep-profile-changes --no-reload 351 | wget -q -O - $(TEST_SHUTDOWN_URL) 352 | 353 | start-server: FORCE 354 | node $(TEST_WWW_SERVER) 355 | 356 | version: FORCE 357 | @echo $(VERSION) 358 | 359 | # end 360 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | wasavi 2 | ====== 3 | 4 | copyright 2012-2017 akahuku, akahuku@gmail.com 5 | 6 | 7 | 8 | This product includes the following libraries: 9 | 10 | * kosian - Library for abstracting browser extension 11 | 12 | 13 | 14 | * blowfish - Javascript Blowfish Library 15 | 16 | 17 | 18 | * sha1.js 19 | 20 | 21 | 22 | * free sound effects 23 | 24 | 25 | 26 | * ES6-Promise 27 | 28 | 29 | 30 | * marked 31 | 32 | 33 | -------------------------------------------------------------------------------- /README-BIN-FILES.md: -------------------------------------------------------------------------------- 1 | A description of binary files 2 | ============================= 3 | 4 | This file describes the role and the source code location of binary files in 5 | the wasavi extension package. 6 | 7 | * src/chrome/consumer_keys.bin 8 | 9 | Blowfish-encrypted JSON file which contains an application key and a secret 10 | key of each account of Dropbox, Google Drive and Microsoft OneDrive. 11 | 12 | The content of this file is generated 13 | by 14 | using as a template. 15 | 16 | * src/chrome/unicode/fftt_general.dat 17 | 18 | The key-value dictionary to extend f/F/t/T command of the vi editor. 19 | By referring to this file, for example, "fa" command can jump to "ä", "à", 20 | "á", "â" ... as well as "a". 21 | 22 | The content of this file is generated 23 | by . 24 | And this script uses [UnicodeData.txt](http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt). 25 | 26 | * src/chrome/unicode/fftt_han_ja.dat 27 | 28 | Same role as fftt_general.dat, but this file contains pronunciation data of 29 | CJK Ideograph characters. These are a lot very much, so data is packed by 30 | special encoding. 31 | 32 | The content of this file is generated 33 | by . 34 | And this script uses Unihan_Readings.txt in [Unihan.zip](http://www.unicode.org/Public/UCD/latest/ucd/Unihan.zip). 35 | 36 | * src/chrome/unicode/linebreak.dat 37 | 38 | Referred when auto-formatting the input line with the specification of 39 | Unicode Line Break Algorithm: . 40 | 41 | The content of this file is generated 42 | by . 43 | And this script uses [LineBreak.txt](http://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt). 44 | -------------------------------------------------------------------------------- /dist/chrome.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /dist/firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "addons": { 3 | "jid1-bmMwuNrx3u5hqQ@jetpack": { 4 | "updates": [ 5 | { 6 | "version": "0.7.737", 7 | "update_link": "https://github.com/akahuku/wasavi/raw/master/dist/wasavi.xpi" 8 | } 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dist/firefox.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
  • 7 | 8 | 0.6.641 9 | 10 | 11 | 12 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 13 | 38.0a1 14 | 45.* 15 | https://github.com/akahuku/wasavi/raw/master/dist/wasavi.xpi 16 | 17 | 18 | 19 | 20 | 21 | 22 |
  • 23 | 24 |
    25 | 26 |
    27 | 28 |
    29 | 30 |
    31 | -------------------------------------------------------------------------------- /dist/opera-blink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /dist/update.rdf: -------------------------------------------------------------------------------- 1 | firefox.rdf -------------------------------------------------------------------------------- /dist/wasavi.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/dist/wasavi.crx -------------------------------------------------------------------------------- /dist/wasavi.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/dist/wasavi.nex -------------------------------------------------------------------------------- /dist/wasavi.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/dist/wasavi.xpi -------------------------------------------------------------------------------- /distribute-scripts: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # qeema 4 | cp node_modules/qeema/qeema.js src/chrome/frontend/qeema.js 5 | 6 | # unistring 7 | cp node_modules/unistring/unistring.js src/chrome/frontend/unistring.js 8 | 9 | # marked 10 | cp node_modules/marked/marked.min.js src/chrome/backend/lib/marked.js 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasavi", 3 | "version": "0.0.1", 4 | "description": "the vi editor in your browser", 5 | "homepage": "http://appsweets.net/wasavi/", 6 | "license": "Apache-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/akahuku/wasavi.git" 10 | }, 11 | "dependencies": { 12 | "unistring": "akahuku/unistring", 13 | "marked": "akahuku/marked", 14 | "qeema": "akahuku/qeema" 15 | }, 16 | "devDependencies": { 17 | "brisket": "akahuku/brisket", 18 | "copy-paste": "^1.3.0" 19 | }, 20 | "scripts": { 21 | "install": "./distribute-scripts", 22 | "update": "./distribute-scripts" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /signxpi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # functions 4 | 5 | usage () { 6 | echo "usage: -s SOURCE-DIRECTORY" 7 | echo " -d DESTINATION-DIRECTORY" 8 | exit 1 9 | } 10 | 11 | test_amo_account () { 12 | if [ -e "~/.amo-account.ini" ]; then 13 | echo "signxpi: error: ~/.amo-account.ini not found." 14 | echo "In this file, please describe AMO account information in the following format:" 15 | echo "AMO_API_KEY=" 16 | echo "AMO_API_SECRET=" 17 | exit 1 18 | fi 19 | } 20 | 21 | test_web_ext () { 22 | which web-ext > /dev/null 23 | local s=$? 24 | if [ $s -ne 0 ]; then 25 | echo "signxpi: web-ext not found." 26 | echo "Please install web-ext via npm." 27 | exit 1 28 | fi 29 | } 30 | 31 | test_amo_account 32 | test_web_ext 33 | 34 | XPI_FILE=wasavi.xpi 35 | AMO_API_KEY=`sed -n -e 's/^\s*AMO_API_KEY\s*=\s*//p' ~/.amo-account.ini` 36 | AMO_API_SECRET=`sed -n -e 's/^\s*AMO_API_SECRET\s*=\s*//p' ~/.amo-account.ini` 37 | 38 | # parse arguments 39 | 40 | while getopts s:d: OPT 41 | do 42 | case $OPT in 43 | s) 44 | SRC_DIR=$OPTARG ;; 45 | d) 46 | DST_DIR=$OPTARG ;; 47 | *) 48 | usage ;; 49 | esac 50 | done 51 | 52 | if [ -z "$SRC_DIR" ]; then 53 | echo "signxpi: error: missing source directory" 54 | usage 55 | fi 56 | if [ -z "$DST_DIR" ]; then 57 | echo "signxpi: error: missing destination directory" 58 | usage 59 | fi 60 | 61 | # sign 62 | 63 | web-ext sign \ 64 | --api-key=${AMO_API_KEY} \ 65 | --api-secret=${AMO_API_SECRET} \ 66 | --source-dir=${SRC_DIR} \ 67 | --artifacts-dir=${DST_DIR} 68 | 69 | # rename 70 | 71 | ( 72 | cd $DST_DIR 73 | for f in *.xpi 74 | do 75 | if [ $f = $XPI_FILE ]; then 76 | continue 77 | fi 78 | mv $f $XPI_FILE 79 | echo "$f renamed to $XPI_FILE" 80 | done 81 | ) 82 | 83 | -------------------------------------------------------------------------------- /src/chrome/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /src/chrome/NOTICE: -------------------------------------------------------------------------------- 1 | ../../NOTICE -------------------------------------------------------------------------------- /src/chrome/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /src/chrome/_locales/core.json: -------------------------------------------------------------------------------- 1 | { 2 | "wasavi_locale": { 3 | "message": "english (USA)" 4 | }, 5 | "wasavi_locale_code": { 6 | "message": "en-US" 7 | }, 8 | "wasavi_name": { 9 | "message": "wasavi", 10 | "description": "" 11 | }, 12 | "wasavi_desc": { 13 | "message": "vi editor for any web page.", 14 | "description": "" 15 | }, 16 | "option_title": { 17 | "message": "wasavi options", 18 | "description": "" 19 | }, 20 | "option_exrc_desc": { 21 | "message": "Enter initialization commands for wasavi. These commands will be executed every time wasavi is launched. You can write any ex commands you like.", 22 | "description": "" 23 | }, 24 | "option_quick_activation_on": { 25 | "message": "when an element is focused", 26 | "description": "" 27 | }, 28 | "option_quick_activation_off": { 29 | "message": "when a key combination is entered", 30 | "description": "" 31 | }, 32 | "option_site_overrides_head": { 33 | "message": "Site overrides", 34 | "description": "" 35 | }, 36 | "option_site_overrides_tips": { 37 | "message": "* Put any URL to control wasavi behavior for each site.\n* A line starting with [c];[/c] or [c]#[/c] is treated as comment.\n* Each valid line consists of three components delimited by space: URL, CSS-selector, action.\n* You can use wildcard [c]*[/c] and [c]?[/c] in URL.\n* action is [c]block[/c] or [c]:set[/c] command of wasavi. block action prevents wasavi from launch. set command will be evaluated after exrc.", 38 | "description": "" 39 | }, 40 | "option_target_elements_desc": { 41 | "message": "* Format is <(modifier-)* key>,...\nFor example, , means \"Launch wasavi when ctrl+enter or insert is pressed.\"\n* Following keys can be used:\n[c]backspace[/c] [c]bs[/c] [c]tab[/c] [c]enter[/c] [c]return[/c] [c]ret[/c] [c]pageup[/c] [c]pagedown[/c] [c]end[/c] [c]home[/c] [c]left[/c] [c]up[/c] [c]right[/c] [c]down[/c] [c]insert[/c] [c]ins[/c] [c]delete[/c] [c]del[/c] [c],[/c] [c]comma[/c] [c].[/c] [c]dot[/c] [c]period[/c] [c]/[/c] [c]slash[/c] [c]\\[[/c] [c]\\\\[/c] [c]backslash[/c] [c]\\][/c]\n[c]f1[/c] to [c]f12[/c], [c]a[/c] to [c]z[/c], [c]0[/c] to [c]9[/c]\n* Following keys can be used as modifier: [c]s[/c] (for SHIFT), [c]c[/c] (for CTRL).\n* If text field is left empty default shortcut , is used.", 42 | "description": "" 43 | }, 44 | "option_save": { 45 | "message": "Save", 46 | "description": "" 47 | }, 48 | "option_saved": { 49 | "message": "saved.", 50 | "description": "" 51 | }, 52 | "option_exrc_head": { 53 | "message": "exrc", 54 | "description": "" 55 | }, 56 | "option_target_elements_head": { 57 | "message": "target elements", 58 | "description": "" 59 | }, 60 | "option_starting_type_head": { 61 | "message": "Launch wasavi", 62 | "description": "" 63 | }, 64 | "option_font_family_head": { 65 | "message": "font family", 66 | "description": "" 67 | }, 68 | "option_preferred_storage_head": { 69 | "message": "preferred storage", 70 | "description": "" 71 | }, 72 | "option_init_head": { 73 | "message": "Initialize", 74 | "description": "" 75 | }, 76 | "option_init_desc": { 77 | "message": "Initialize all options", 78 | "description": "" 79 | }, 80 | "option_init_confirm": { 81 | "message": "Are you sure?", 82 | "description": "" 83 | }, 84 | "option_debug_head": { 85 | "message": "Debug", 86 | "description": "" 87 | }, 88 | "option_log_desc": { 89 | "message": "Enable logging", 90 | "description": "" 91 | }, 92 | "option_capture_normal": { 93 | "message": "Capture shortcut key...", 94 | "description": "" 95 | }, 96 | "option_capture_wait": { 97 | "message": "Press your favorite shortcut key...", 98 | "description": "" 99 | }, 100 | "option_sounds_head": { 101 | "message": "Sounds", 102 | "description": "" 103 | }, 104 | "option_sound_launch": { 105 | "message": "On launching", 106 | "description": "" 107 | }, 108 | "option_sound_beep": { 109 | "message": "On error", 110 | "description": "" 111 | }, 112 | "option_sound_volume": { 113 | "message": "Volume", 114 | "description": "" 115 | }, 116 | "option_upgrade_head": { 117 | "message": "When upgraded", 118 | "description": "" 119 | }, 120 | "option_upgrade_notify": { 121 | "message": "Open wasavi homepage", 122 | "description": "" 123 | }, 124 | "option_readme": { 125 | "message": "README", 126 | "description": "" 127 | }, 128 | "option_license": { 129 | "message": "LICENSE", 130 | "description": "" 131 | }, 132 | "option_notice": { 133 | "message": "NOTICE", 134 | "description": "" 135 | }, 136 | "edit_with_wasavi": { 137 | "message": "Edit with wasavi", 138 | "description": "" 139 | }, 140 | "option_open_button_desc": { 141 | "message": "Open Options Page with a new tab", 142 | "description": "" 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/chrome/_locales/locales.json: -------------------------------------------------------------------------------- 1 | ["en_US","ja"] -------------------------------------------------------------------------------- /src/chrome/_locales/plural_rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "_plural_rule@function": { 3 | "message": "isone(one)" 4 | } 5 | } -------------------------------------------------------------------------------- /src/chrome/backend/lib/ContextMenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * context menu for wasavi 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | (function (global) { 23 | 'use strict'; 24 | 25 | var MENU_EDIT_WITH_WASAVI = 'edit_with_wasavi'; 26 | 27 | function ContextMenu (options) { 28 | this.options = options || {}; 29 | this.ext = require('./kosian/Kosian').Kosian(); 30 | this.init(); 31 | this.build(); 32 | } 33 | 34 | ContextMenu.prototype = { 35 | init: function () {}, 36 | build: function () {}, 37 | getRequestRunPayload: function () { 38 | return {type: 'request-run'}; 39 | }, 40 | getMenuLabel: function (id) { 41 | var mc = this.ext.messageCatalog; 42 | return mc && mc[id].message || id; 43 | } 44 | }; 45 | 46 | function ChromeContextMenu (options) { 47 | ContextMenu.apply(this, arguments); 48 | } 49 | ChromeContextMenu.prototype = Object.create(ContextMenu.prototype, { 50 | build: {value: function () { 51 | var that = this; 52 | 53 | chrome.contextMenus.removeAll(function () { 54 | chrome.contextMenus.create({ 55 | contexts: ['page', 'editable'], 56 | title: chrome.i18n.getMessage(MENU_EDIT_WITH_WASAVI), 57 | onclick: function (info, tab) { 58 | var options; 59 | 60 | if ('frameId' in info) { 61 | options = {frameId: info.frameId}; 62 | } 63 | 64 | chrome.tabs.sendMessage( 65 | tab.id, that.getRequestRunPayload(), options); 66 | } 67 | }); 68 | }); 69 | }} 70 | }); 71 | ChromeContextMenu.prototype.constructor = ContextMenu; 72 | 73 | exports.ContextMenu = function (options) { 74 | if (global.chrome) { 75 | return new ChromeContextMenu(options); 76 | } 77 | return new ContextMenu(options); 78 | }; 79 | })(this); 80 | 81 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 82 | -------------------------------------------------------------------------------- /src/chrome/backend/lib/Memorandum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * page-memorandum manager for wasavi 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | (function (global) { 23 | 'use strict'; 24 | 25 | function Memorandum (options) { 26 | var URL_MAX = 10; 27 | 28 | var ext = require('./kosian/Kosian').Kosian(); 29 | var SHA1 = require('./kosian/SHA1').SHA1; 30 | options || (options = {urlMax: URL_MAX}); 31 | 32 | function get (url) { 33 | var content = ext.storage.getItem(getKey(url)); 34 | if (content) { 35 | content = content.substring(content.indexOf('\t') + 1); 36 | } 37 | return content; 38 | } 39 | 40 | function set (url, content) { 41 | ext.storage.setItem(getKey(url), Date.now() + '\t' + content); 42 | purge(); 43 | } 44 | 45 | function exists (url) { 46 | return ext.storage.exists(getKey(url)); 47 | } 48 | 49 | function getKey (url) { 50 | return 'memo-' + SHA1.calc(url.replace(/#[^#]*/, '')); 51 | } 52 | 53 | function purge () { 54 | var keys = ext.storage.keys() 55 | .filter(function (key) {return key.indexOf('memo-') == 0}) 56 | .sort(function (a, b) { 57 | return parseInt(ext.storage.getItem(a), 10) - parseInt(ext.storage.getItem(b), 10); 58 | }); 59 | 60 | while (keys.length > options.urlMax) { 61 | ext.storage.setItem(keys[0], undefined); 62 | keys.shift(); 63 | } 64 | } 65 | 66 | this.get = get; 67 | this.set = set; 68 | this.exists = exists; 69 | } 70 | 71 | exports.Memorandum = function (options) { 72 | return new Memorandum(options); 73 | }; 74 | })(this); 75 | 76 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 77 | -------------------------------------------------------------------------------- /src/chrome/backend/lib/RuntimeOverwriteSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * runtime overwrite settings 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | (function (global) { 23 | 'use strict'; 24 | 25 | function RuntimeOverwriteSettings (options) { 26 | var ROS_URL_MAX = 30; 27 | var ROS_MATCH_RATIO = 0.8; 28 | 29 | var ext = require('./kosian/Kosian').Kosian(); 30 | var SHA1 = require('./kosian/SHA1').SHA1; 31 | var similarityComputer = require('./SimilarityComputer').SimilarityComputer(); 32 | var cache; 33 | options || (options = { 34 | urlMax: ROS_URL_MAX, 35 | matchRatio: ROS_MATCH_RATIO 36 | }); 37 | 38 | function getKeyParts (url, path) { 39 | var re = /^([^?#]*)([?#].*)?$/.exec(url); 40 | var query = re[2] || ''; 41 | return { 42 | url:ext.isDev ? url : SHA1.calc(url), 43 | urlBase:ext.isDev ? re[1] : SHA1.calc(re[1]), 44 | query:query, 45 | queryData:similarityComputer.getNgram(query), 46 | nodePath:path 47 | }; 48 | } 49 | 50 | function findCacheIndex (keyParts) { 51 | for (var i = 0, goal = cache.length; i < goal; i++) { 52 | if (cache[i].url == keyParts.url) { 53 | return i; 54 | } 55 | } 56 | return false; 57 | } 58 | 59 | function get (url, nodePath) { 60 | nodePath || (nodePath = ''); 61 | if (nodePath == '') return ''; 62 | 63 | if (!cache) { 64 | cache = ext.utils.parseJson(ext.storage.getItem('ros'), []); 65 | } 66 | if (!('length' in cache)) { 67 | cache = []; 68 | } 69 | 70 | var keyParts = getKeyParts(url, nodePath); 71 | var index = -1, qscoreMax = 0, pscoreMax = 0; 72 | for (var i = 0, goal = cache.length; i < goal; i++) { 73 | if (keyParts.url == cache[i].url) { 74 | index = i; 75 | break; 76 | } 77 | if (keyParts.urlBase != cache[i].urlBase) continue; 78 | 79 | var qscore = similarityComputer.getNgramRatio2( 80 | keyParts.query, cache[i].query, 81 | keyParts.queryData, cache[i].queryData); 82 | var pscore = similarityComputer.getLevenshteinRatio( 83 | keyParts.nodePath, cache[i].nodePath); 84 | 85 | if (qscore >= options.matchRatio && qscore > qscoreMax 86 | && pscore >= options.matchRatio && pscore > pscoreMax) { 87 | index = i; 88 | qscoreMax = qscore; 89 | pscoreMax = pscore; 90 | } 91 | } 92 | 93 | if (index >= 0) { 94 | var item = cache.splice(index, 1)[0]; 95 | cache.unshift(item); 96 | return item.script; 97 | } 98 | 99 | return ''; 100 | } 101 | 102 | function set (url, nodePath, script) { 103 | nodePath || (nodePath = ''); 104 | if (nodePath == '') return; 105 | 106 | if (!cache) { 107 | cache = []; 108 | } 109 | 110 | var keyParts = getKeyParts(url, nodePath); 111 | var index = findCacheIndex(keyParts); 112 | var item = index === false ? keyParts : cache.splice(index, 1)[0]; 113 | item.script = script; 114 | cache.unshift(item); 115 | 116 | while (cache.length > options.urlMax) { 117 | cache.pop(); 118 | } 119 | 120 | ext.storage.setItem('ros', JSON.stringify(cache)); 121 | } 122 | 123 | this.get = get; 124 | this.set = set; 125 | } 126 | 127 | function create (options) { 128 | return new RuntimeOverwriteSettings(options); 129 | } 130 | 131 | exports.RuntimeOverwriteSettings = create; 132 | })(this); 133 | 134 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 135 | -------------------------------------------------------------------------------- /src/chrome/backend/lib/SimilarityComputer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * string similarity utility 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | (function () { 23 | 'use strict'; 24 | 25 | function SimilarityComputer (unitSize) { 26 | this.unitSize = unitSize || 3; 27 | } 28 | 29 | SimilarityComputer.prototype = { 30 | getNgram: function (text) { 31 | text = text.replace(/\s/g, ''); 32 | var result = {}; 33 | for (var i = 0, goal = text.length - (this.unitSize - 1); i < goal; i++) { 34 | result[text.substr(i, this.unitSize)] = 1; 35 | } 36 | return result; 37 | }, 38 | 39 | getCommonLength: function (t1ngram, t2ngram) { 40 | var result = 0; 41 | for (var i in t1ngram) { 42 | i in t2ngram && result++; 43 | } 44 | return result; 45 | }, 46 | 47 | getUnionLength: function (t1ngram, t2ngram) { 48 | return Object.keys(t1ngram).length + Object.keys(t2ngram).length; 49 | }, 50 | 51 | getNgramRatio: function (t1, t2) { 52 | var t1ngram, t2ngram; 53 | 54 | if (t1 && t2 && typeof t1 == 'object' && typeof t2 == 'object') { 55 | t1ngram = t1; 56 | t2ngram = t2; 57 | } 58 | else if (typeof t1 == 'string' && typeof t2 == 'string') { 59 | if (t1.length < this.unitSize || t2.length < this.unitSize) { 60 | return this.getLevenshteinRatio(t1, t2); 61 | } 62 | t1ngram = this.getNgram(t1 + ''); 63 | t2ngram = this.getNgram(t2 + ''); 64 | } 65 | else { 66 | throw new Error('invalid arguments'); 67 | } 68 | 69 | var commonLength = this.getCommonLength(t1ngram, t2ngram); 70 | var unionLength = this.getUnionLength(t1ngram, t2ngram); 71 | var result = 2.0 * commonLength / unionLength; 72 | return result; 73 | }, 74 | 75 | getLevenshteinRatio: function (t1, t2) { 76 | if (t1 == '' && t2 == '') return 1.0; 77 | 78 | var x = t1.length; 79 | var y = t2.length; 80 | var m = []; 81 | for (var i = 0; i <= x; i++) { 82 | m[i] = []; 83 | m[i][0] = i; 84 | } 85 | for (var i = 0; i <= y; i++) { 86 | m[0][i] = i; 87 | } 88 | for (var i = 1; i <= x; i++) { 89 | for (var j = 1; j <= y; j++) { 90 | var cost = t1.charAt(i - 1) == t2.charAt(j - 1) ? 0 : 1; 91 | m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + cost); 92 | } 93 | } 94 | var result = 1.0 - (m[x][y] / Math.max(x, y)); 95 | return result; 96 | }, 97 | 98 | getNgramRatio2: function (t1, t2, t1data, t2data) { 99 | if (t1.length < this.unitSize || t2.length < this.unitSize) { 100 | return this.getNgramRatio(t1, t2); 101 | } 102 | else { 103 | return this.getNgramRatio(t1data, t2data); 104 | } 105 | } 106 | }; 107 | 108 | function create (unitsize) { 109 | return new SimilarityComputer(unitsize); 110 | } 111 | 112 | exports.SimilarityComputer = create; 113 | })(); 114 | 115 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 116 | -------------------------------------------------------------------------------- /src/chrome/backend/lib/SyncStorage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * synchronized storage wrapper for wasavi 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | (function (global) { 23 | 'use strict'; 24 | 25 | function SyncStorage (options) { 26 | this.options = options || {}; 27 | this.ext = require('./kosian/Kosian').Kosian(); 28 | } 29 | 30 | SyncStorage.prototype = { 31 | clear: function (callback) {this.ext.emit(callback)}, 32 | get: function (keys, callback) {this.ext.emit(callback, {})}, 33 | getBytesInUse: function (keys, callback) {this.ext.emit(callback, 0)}, 34 | remove: function (keys, callback) {this.ext.emit(callback)}, 35 | set: function (items, callback) {this.ext.emit(callback)} 36 | }; 37 | 38 | function ChromeSyncStorage (options) { 39 | SyncStorage.apply(this, arguments); 40 | chrome.identity.onSignInChanged.addListener(function (info, signedIn) { 41 | this.ext.emit(this.options.onSignInChanged); 42 | }.bind(this)) 43 | } 44 | ChromeSyncStorage.prototype = Object.create(SyncStorage.prototype, { 45 | clear: {value: function (callback) { 46 | return chrome.storage.sync.clear(callback); 47 | }}, 48 | get: {value: function (keys, callback) { 49 | return chrome.storage.sync.get(keys, callback); 50 | }}, 51 | getBytesInUse: {value: function (keys, callback) { 52 | return chrome.storage.sync.getBytesInUse(keys, callback); 53 | }}, 54 | remove: {value: function (keys, callback) { 55 | return chrome.storage.sync.remove(keys, callback); 56 | }}, 57 | set: {value: function (items, callback) { 58 | return chrome.storage.sync.set(items, callback); 59 | }} 60 | }); 61 | ChromeSyncStorage.prototype.constructor = SyncStorage; 62 | 63 | exports.SyncStorage = function (options) { 64 | let isChrome = false; 65 | do { 66 | if (!global.chrome) break; 67 | if (!('storage' in chrome)) break; 68 | if (!('sync' in chrome.storage)) break; 69 | if (!('identity' in chrome)) break; 70 | if (!('onSignInChanged' in chrome.identity)) break; 71 | if (typeof chrome.identity.onSignInChanged != 'object') break; 72 | if (!('addListener' in chrome.identity.onSignInChanged)) break; 73 | if (typeof chrome.identity.onSignInChanged.addListener != 'function') break; 74 | 75 | isChrome = true; 76 | } while (false); 77 | 78 | return isChrome ? 79 | new ChromeSyncStorage(options) : 80 | new SyncStorage(options); 81 | }; 82 | })(this); 83 | 84 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 85 | -------------------------------------------------------------------------------- /src/chrome/backend/lib/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * enumerate the scripts which this app uses 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | loadScripts( 23 | 'SimilarityComputer.js', 24 | 'RuntimeOverwriteSettings.js', 25 | 'ContextMenu.js', 26 | 'Memorandum.js', 27 | 'SyncStorage.js', 28 | typeof window.Promise == 'undefined' ? 'es6-promise.min.js' : null, 29 | 'marked.js', 30 | 'main.js' 31 | ); 32 | -------------------------------------------------------------------------------- /src/chrome/backend/main.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 22 | 23 | 24 | 25 | background for wasavi 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    34 | 35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | 53 |
    54 |
    55 |
    56 | Press ^C to cancel 57 |
    58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/chrome/consumer_keys.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "dropbox": { 3 | "key": "INSERT-YOUR-DROPBOX-APP-KEY", 4 | "secret": "INSERT-YOUR-DROPBOX-APP-SECRET", 5 | "callback": "https://ss1.xrea.com/appsweets.net/authorized.html?fs=dropbox", 6 | "root": "dropbox" 7 | }, 8 | "gdrive": { 9 | "key": "INSERT-YOUR-GDRIVE-APP-KEY", 10 | "secret": "INSERT-YOUR-GDRIVE-APP-SECRET", 11 | "callback": "http://appsweets.net/authorized.html?fs=gdrive", 12 | "root": "" 13 | }, 14 | "onedrive": { 15 | "key": "INSERT-YOUR-ONEDRIVE-APP-KEY", 16 | "secret": "INSERT_YOUR-ONEDRIVE-APP-SECRET", 17 | "callback": "http://appsweets.net/authorized.html", 18 | "root": "" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/chrome/frontend/extension_wrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * wasavi: vi clone implemented in javascript 3 | * ============================================================================= 4 | * 5 | * 6 | * @author akahuku@gmail.com 7 | */ 8 | /** 9 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | !this.WasaviExtensionWrapper && (function (global) { 27 | /* <<<1 consts */ 28 | const IS_GECKO = 'InstallTrigger' in global; 29 | const IS_FX_WEBEXT = IS_GECKO && global.chrome && global.chrome.extension; 30 | /* >>> */ 31 | 32 | /* <<<1 vars */ 33 | var extensionName = 'wasavi'; 34 | var externalFrameURL = 'http://wasavi.appsweets.net/'; 35 | var externalSecureFrameURL = 'https://wasavi.appsweets.net/'; 36 | /* >>> */ 37 | 38 | /** 39 | * <<<1 url information class 40 | * ---------------- 41 | */ 42 | 43 | function UrlInfo (optionsUrl, internalUrl) { 44 | this.optionsUrl = optionsUrl; 45 | this.internalUrl = internalUrl; 46 | } 47 | 48 | UrlInfo.prototype = { 49 | eq: function (u1, u2) { 50 | return (u1 || '').replace(/\?.*/, '') 51 | == (u2 || '').replace(/\?.*/, ''); 52 | }, 53 | get externalUrl () {return externalFrameURL}, 54 | get externalSecureUrl () {return externalSecureFrameURL}, 55 | get isInternal () { 56 | return this.eq(window.location.href, this.internalUrl); 57 | }, 58 | get isExternal () { 59 | return this.eq(window.location.href, this.externalUrl) 60 | || this.eq(window.location.href, this.externalSecureUrl); 61 | }, 62 | get isAny () { 63 | return this.isInternal || this.isExternal; 64 | }, 65 | get frameSource () { 66 | if (this.internalUrl) { 67 | return this.internalUrl; 68 | } 69 | else { 70 | return window.location.protocol == 'https:' ? 71 | this.externalSecureUrl : this.externalUrl; 72 | } 73 | } 74 | }; 75 | /* >>> */ 76 | 77 | /** 78 | * <<<1 extension wrapper base class 79 | * ---------------- 80 | */ 81 | 82 | function ExtensionWrapper () { 83 | this.tabId = null; 84 | this.requestNumber = 0; 85 | } 86 | ExtensionWrapper.prototype = { 87 | get name () {return extensionName}, 88 | isTopFrame: function () {return global.window == window.top}, 89 | postMessage: function (data, callback) { 90 | var type; 91 | var requestNumber = this.getNewRequestNumber(); 92 | 93 | data || (data = {}); 94 | 95 | if ('type' in data) { 96 | type = data.type; 97 | delete data.type; 98 | } 99 | 100 | this.doPostMessage({ 101 | type:type || 'unknown-command', 102 | tabId:this.tabId, 103 | requestNumber:requestNumber, 104 | data:data 105 | }, callback); 106 | 107 | return requestNumber; 108 | }, 109 | doPostMessage: function (data, callback) {}, 110 | connect: function (type, callback) { 111 | this.doConnect(); 112 | this.doPostMessage({ 113 | type:type || 'init', 114 | tabId:this.tabId, 115 | requestNumber:this.getNewRequestNumber(), 116 | data:{url:window.location.href} 117 | }, callback); 118 | }, 119 | doConnect: function () {}, 120 | disconnect: function () { 121 | this.doDisconnect(); 122 | }, 123 | doDisconnect: function () {}, 124 | setMessageListener: function (handler) {}, 125 | addMessageListener: function (handler) {}, 126 | removeMessageListener: function (handler) {}, 127 | runCallback: function () { 128 | var args = Array.prototype.slice.call(arguments); 129 | var callback = args.shift(); 130 | if (typeof callback != 'function') { 131 | return; 132 | } 133 | return callback.apply(null, args); 134 | }, 135 | getUniqueId: function () { 136 | return this.name 137 | + '_' + Date.now() 138 | + '_' + Math.floor(Math.random() * 0x10000); 139 | }, 140 | getNewRequestNumber: function () { 141 | this.requestNumber = (this.requestNumber + 1) & 0xffff; 142 | return this.requestNumber; 143 | }, 144 | getMessage: function (messageId) {}, 145 | setClipboard: function (data) { 146 | if (IS_GECKO) { 147 | let buffer = document.getElementById('wasavi_fx_clip'); 148 | buffer.value = data; 149 | buffer.focus(); 150 | buffer.select(); 151 | document.execCommand('cut'); 152 | } 153 | else { 154 | this.postMessage({type:'set-clipboard', data:data}); 155 | } 156 | }, 157 | getClipboard: function () { 158 | var self = this; 159 | var args = Array.prototype.slice.call(arguments); 160 | var callback = args.shift(); 161 | this.postMessage({type:'get-clipboard'}, function (req) { 162 | let clipboardData = (req && req.data || '').replace(/\r\n/g, '\n'); 163 | args.unshift(clipboardData); 164 | callback.apply(null, args); 165 | }); 166 | }, 167 | getPageContextScriptSrc: function (path) { 168 | return ''; 169 | }, 170 | ensureRun: function () { 171 | var args = Array.prototype.slice.call(arguments); 172 | var callback = args.shift(); 173 | var doc; 174 | try { 175 | doc = document; 176 | doc.body; 177 | } 178 | catch (e) { 179 | return; 180 | } 181 | if (doc.readyState == 'interactive' 182 | || doc.readyState == 'complete') { 183 | callback.apply(null, args); 184 | callback = args = null; 185 | } 186 | else { 187 | doc.addEventListener( 188 | 'DOMContentLoaded', 189 | function handleDCL (e) { 190 | doc.removeEventListener(e.type, handleDCL, false); 191 | callback.apply(null, args); 192 | e = callback = args = null; 193 | }, 194 | false 195 | ); 196 | } 197 | } 198 | }; 199 | 200 | ExtensionWrapper.create = function (opts) { 201 | opts || (opts = {}); 202 | 'extensionName' in opts && (extensionName = opts.extensionName); 203 | 'externalFrameURL' in opts && (externalFrameURL = opts.externalFrameURL); 204 | 'externalSecureUrl' in opts && (externalSecureUrl = opts.externalSecureUrl); 205 | 206 | if (window.chrome) return new ChromeExtensionWrapper; 207 | if (global.chrome) return new ChromeExtensionWrapper; 208 | return new ExtensionWrapper; 209 | }; 210 | ExtensionWrapper.IS_GECKO = IS_GECKO; 211 | ExtensionWrapper.IS_FX_WEBEXT = IS_FX_WEBEXT; 212 | ExtensionWrapper.urlInfo = new UrlInfo; 213 | /* >>> */ 214 | 215 | /** 216 | * <<<1 extension wrapper class for chrome 217 | * ---------------- 218 | */ 219 | 220 | function ChromeExtensionWrapper () { 221 | ExtensionWrapper.apply(this, arguments); 222 | 223 | var that = this; 224 | var onMessageHandlers = []; 225 | 226 | function handleMessage (req, sender, response) { 227 | for (const handler of onMessageHandlers) { 228 | handler(req, sender, response); 229 | } 230 | } 231 | 232 | this.constructor = ExtensionWrapper; 233 | this.runType = 'chrome-extension'; 234 | this.doPostMessage = function (data, callback) { 235 | try { 236 | chrome.runtime.sendMessage(data, callback); 237 | } 238 | catch (e) {} 239 | }; 240 | this.doConnect = function () { 241 | chrome.runtime.onMessage.addListener(handleMessage); 242 | }; 243 | this.doDisconnect = function () { 244 | onMessageHandlers.length = 0; 245 | chrome.runtime.onMessage.removeListener(handleMessage); 246 | }; 247 | this.setMessageListener = function (handler) { 248 | onMessageHandlers = [handler]; 249 | }; 250 | this.addMessageListener = function (handler) { 251 | var index = onMessageHandlers.indexOf(handler); 252 | if (index < 0) { 253 | onMessageHandlers.push(handler); 254 | } 255 | }; 256 | this.removeMessageListener = function (handler) { 257 | var index = onMessageHandlers.indexOf(handler); 258 | if (index >= 0) { 259 | onMessageHandlers.splice(index, 1); 260 | } 261 | }; 262 | this.getMessage = function (messageId) { 263 | return chrome.i18n.getMessage(messageId); 264 | }; 265 | this.getPageContextScriptSrc = function () { 266 | return chrome.runtime.getURL('scripts/page_context.js'); 267 | }; 268 | this.urlInfo = new function () { 269 | return new UrlInfo( 270 | chrome.runtime.getURL('options.html'), 271 | chrome.runtime.getURL('wasavi.html') 272 | ); 273 | }; 274 | } 275 | ChromeExtensionWrapper.prototype = ExtensionWrapper.prototype; 276 | /* >>> */ 277 | 278 | /* <<<1 bootstrap */ 279 | ExtensionWrapper.urlInfo.isExternal && 280 | document.documentElement.setAttribute('data-wasavi-present', 1); 281 | global.WasaviExtensionWrapper = ExtensionWrapper; 282 | /* >>> */ 283 | 284 | })(this); 285 | 286 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker fmr=<<<,>>> : 287 | -------------------------------------------------------------------------------- /src/chrome/frontend/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * wasavi: vi clone implemented in javascript 3 | * ============================================================================= 4 | * 5 | * 6 | * @author akahuku@gmail.com 7 | */ 8 | /** 9 | * Copyright 2012-2017 akahuku, akahuku@gmail.com 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | (function (g) { 25 | 26 | 'use strict'; 27 | 28 | const BRACKETS = '[{(<"\'``\'">)}]'; 29 | const CLOSE_BRACKETS = BRACKETS.substring(BRACKETS.length / 2); 30 | 31 | const LOG_PROMISE = false; 32 | const LOG_EX = false; 33 | const LOG_MAP_MANAGER = false; 34 | const LOG_LAST_SIMPLE_COMMAND = false; 35 | 36 | g.Wasavi = Object.defineProperties({}, { 37 | IS_GECKO: {value: 'InstallTrigger' in g}, 38 | 39 | BRACKETS: {value: BRACKETS}, 40 | CLOSE_BRACKETS: {value: CLOSE_BRACKETS}, 41 | 42 | LINE_NUMBER_MAX_WIDTH: {value: 6}, 43 | LINE_NUMBER_RELATIVE_WIDTH: {value: 2}, 44 | 45 | COMPOSITION_CLASS: {value: 'wasavi_composition'}, 46 | LEADING_CLASS: {value: 'wasavi_leading'}, 47 | MARK_CLASS: {value: 'wasavi_mark'}, 48 | EMPHASIS_CLASS: {value: 'wasavi_em'}, 49 | CURSOR_SPAN_CLASS: {value: 'wasavi_command_cursor_span'}, 50 | BOUND_CLASS: {value: 'wasavi_bound'}, 51 | 52 | MIGEMO_EXTENSION_ID: {value: 'dfccgbheolnlopfmahkcjiefggclmadb'}, 53 | MIGEMO_GET_REGEXP_STRING: {value: 'getRegExpString'}, 54 | 55 | LOG_PROMISE: {value: LOG_PROMISE}, 56 | LOG_EX: {value: LOG_EX}, 57 | LOG_MAP_MANAGER: {value: LOG_MAP_MANAGER}, 58 | LOG_LAST_SIMPLE_COMMAND: {value: LOG_LAST_SIMPLE_COMMAND} 59 | }); 60 | 61 | })(typeof global == 'object' ? global : window); 62 | 63 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 64 | -------------------------------------------------------------------------------- /src/chrome/images/appsweets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/images/appsweets.png -------------------------------------------------------------------------------- /src/chrome/images/banner.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/images/banner.xcf -------------------------------------------------------------------------------- /src/chrome/images/icon016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/images/icon016.png -------------------------------------------------------------------------------- /src/chrome/images/icon048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/images/icon048.png -------------------------------------------------------------------------------- /src/chrome/images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/images/icon128.png -------------------------------------------------------------------------------- /src/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_wasavi_name__", 4 | "short_name": "__MSG_wasavi_name__", 5 | "version": "0.0.1", 6 | "description": "__MSG_wasavi_desc__", 7 | "default_locale": "en_US", 8 | "applications": { 9 | "gecko": { 10 | "id": "jid1-bmMwuNrx3u5hqQ@jetpack", 11 | "strict_min_version": "42.0", 12 | "update_url": "https://github.com/akahuku/wasavi/raw/master/dist/firefox.json" 13 | } 14 | }, 15 | "icons": { 16 | "16": "images/icon016.png", 17 | "48": "images/icon048.png", 18 | "128": "images/icon128.png" 19 | }, 20 | "permissions": [ 21 | "tabs", "clipboardRead", "clipboardWrite", "contextMenus", 22 | "identity", "storage", 23 | "https://*.dropboxapi.com/*", 24 | "https://*.google.com/*", "https://*.googleapis.com/*", "https://*.googleusercontent.com/*", 25 | "https://apis.live.net/*" 26 | ], 27 | "background": { 28 | "page": "backend/main.html" 29 | }, 30 | "content_scripts": [ 31 | { 32 | "matches": [ 33 | "http://*/*", 34 | "https://*/*" 35 | ], 36 | "exclude_matches": [ 37 | "http://wasavi.appsweets.net/*", 38 | "https://wasavi.appsweets.net/*" 39 | ], 40 | "js": [ 41 | "frontend/extension_wrapper.js", 42 | "frontend/agent.js" 43 | ], 44 | "run_at": "document_start", 45 | "all_frames": true, 46 | "match_about_blank": true 47 | }, 48 | { 49 | "matches": [ 50 | "http://wasavi.appsweets.net/*", 51 | "https://wasavi.appsweets.net/*" 52 | ], 53 | "js": [ 54 | "frontend/extension_wrapper.js", 55 | "frontend/init.js", 56 | "frontend/utils.js", 57 | "frontend/unistring.js", 58 | "frontend/unicode_utils.js", 59 | "frontend/qeema.js", 60 | "frontend/classes.js", 61 | "frontend/classes_ex.js", 62 | "frontend/classes_undo.js", 63 | "frontend/classes_subst.js", 64 | "frontend/classes_search.js", 65 | "frontend/classes_ui.js", 66 | "frontend/wasavi.js" 67 | ], 68 | "run_at": "document_start", 69 | "all_frames": true 70 | } 71 | ], 72 | "web_accessible_resources": [ 73 | "wasavi.html", 74 | "scripts/page_context.js" 75 | ], 76 | "options_ui": { 77 | "page": "options.html", 78 | "open_in_tab": true 79 | }, 80 | "homepage_url": "http://appsweets.net/", 81 | "update_url":"https://github.com/akahuku/wasavi/raw/master/dist/chrome.xml" 82 | } 83 | -------------------------------------------------------------------------------- /src/chrome/options.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 22 | 23 | 24 | 25 | 26 | __MSG_option_title__ 27 | 217 | 218 | 219 | 220 | 221 | 222 |

    223 |
    224 | __MSG_option_title__ 225 |
    226 | 232 |

    233 | 234 |
    235 |

    __MSG_option_exrc_head__

    236 |
    237 | 238 |
    __MSG_option_exrc_desc__
    239 |
    240 |
    241 | 242 |
    243 |

    __MSG_option_target_elements_head__

    244 |
    245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 |
    256 |
    257 | 258 |
    259 |

    __MSG_option_starting_type_head__

    260 |
    261 | 262 | 263 | 264 |
    265 | 266 | 271 |
    __MSG_option_target_elements_desc__
    272 |
    273 |
    274 |
    275 | 276 |
    277 |

    __MSG_option_site_overrides_head__

    278 |
    279 | 280 |
    __MSG_option_site_overrides_tips__
    281 |
    282 |
    283 | 284 |
    285 |

    __MSG_option_font_family_head__

    286 |
    287 | 288 |
    289 |
    290 | 291 |
    292 |

    __MSG_option_preferred_storage_head__

    293 |
    294 | 295 | 296 | 297 | 298 |
    299 |
    300 | 301 |
    302 |

    __MSG_option_upgrade_head__

    303 |
    304 | 305 |
    306 |
    307 | 308 |
    309 |

    __MSG_option_init_head__

    310 |
    311 | 312 | 313 |
    314 |
    315 | 316 |
    317 |

    __MSG_option_debug_head__

    318 |
    319 | 320 |
    321 |
    322 | 323 |
    324 |
    325 | 326 |
    327 |
    328 | 329 | __MSG_option_saved__ 330 |
    331 |
    332 | 333 |
    334 | 335 | 336 | 337 | -------------------------------------------------------------------------------- /src/chrome/scripts/page_context.js: -------------------------------------------------------------------------------- 1 | /** 2 | * page-context script for wasavi frontend 3 | * 4 | * @author akahuku@gmail.com 5 | */ 6 | /** 7 | * Copyright 2012-2016 akahuku, akahuku@gmail.com 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | !(function(win,doc){ 23 | 24 | doc.addEventListener('WasaviRequestGetContent', function (e) { 25 | var className = e.detail; 26 | var node = doc.getElementsByClassName(className)[0]; 27 | if (!node) return; 28 | 29 | var result = ''; 30 | if (node.CodeMirror) 31 | try {result = node.CodeMirror.getValue()} catch (ex) {result = ''} 32 | else if (node.classList.contains('ace_editor') && win.ace) 33 | try {result = win.ace.edit(node).getValue()} catch(ex) {result = ''} 34 | 35 | var ev = doc.createEvent('CustomEvent'); 36 | ev.initCustomEvent('WasaviResponseGetContent', false, false, className + '\t' + result); 37 | doc.dispatchEvent(ev); 38 | }, false); 39 | 40 | doc.addEventListener('WasaviRequestSetContent', function (e) { 41 | var delimiterIndex = e.detail.indexOf('\t'); 42 | var className = e.detail.substring(0, delimiterIndex); 43 | var content = e.detail.substring(delimiterIndex + 1); 44 | var node = doc.getElementsByClassName(className)[0]; 45 | if (!node) return; 46 | 47 | node.classList.remove(className); 48 | if (node.CodeMirror) 49 | try {node.CodeMirror.setValue(content)} catch (ex) {} 50 | else if (node.classList.contains('ace_editor') && win.ace) 51 | try {win.ace.edit(node).setValue(content)} catch(ex) {} 52 | }, false); 53 | })(window,document); 54 | 55 | // vim:set ts=4 sw=4 fileencoding=UTF-8 fileformat=unix filetype=javascript fdm=marker : 56 | -------------------------------------------------------------------------------- /src/chrome/sounds/beep.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/sounds/beep.mp3 -------------------------------------------------------------------------------- /src/chrome/sounds/beep.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/sounds/beep.ogg -------------------------------------------------------------------------------- /src/chrome/sounds/launch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/sounds/launch.mp3 -------------------------------------------------------------------------------- /src/chrome/sounds/launch.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/sounds/launch.ogg -------------------------------------------------------------------------------- /src/chrome/styles/wasavi.css: -------------------------------------------------------------------------------- 1 | /* 2 | * styles for wasavi 3 | * 4 | * @author akahuku@gmail.com 5 | * 6 | * Copyright 2012-2016 akahuku, akahuku@gmail.com 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | #wasavi_container { 22 | } 23 | 24 | #wasavi_textwidth_guide { 25 | display:none; 26 | position:fixed; 27 | box-sizing:border-box; 28 | left:0; top:0; width:32px; 29 | padding:4px 0 0 4px; 30 | border-left:1px solid silver; 31 | font-size:xx-small; 32 | font-style:italic; 33 | color:silver; 34 | } 35 | 36 | #wasavi_cursor_line { 37 | position:fixed; 38 | left:0; 39 | right:0; 40 | height:32px; 41 | border-top:1px solid silver; 42 | } 43 | 44 | #wasavi_cursor_column { 45 | position:fixed; 46 | top:0; 47 | bottom:0; 48 | width:32px; 49 | border-left:1px solid silver; 50 | } 51 | 52 | #wasavi_singleline_scaler { 53 | position:fixed; 54 | margin:0; 55 | padding:0; 56 | /**/ 57 | text-decoration:none; 58 | text-shadow:none; 59 | white-space:pre; 60 | color:#fff; 61 | background-color:#000; 62 | left:0px; 63 | top:0px; 64 | visibility:hidden; 65 | } 66 | 67 | /* 68 | * editor screen 69 | */ 70 | 71 | #wasavi_editor { 72 | position:fixed; 73 | left:0; top:0; right:0; bottom:0; 74 | margin:0; 75 | padding:0; 76 | border:none; 77 | /**/ 78 | overflow-x:hidden; 79 | overflow-y:scroll; 80 | counter-reset:na 0 nr 0; 81 | } 82 | 83 | #wasavi_editor > div { 84 | margin:0; 85 | padding:0; 86 | min-height:/**/1px/**/; 87 | line-height:1.2; 88 | white-space:pre-wrap; 89 | word-wrap:break-word; 90 | counter-increment:na nr -1; 91 | } 92 | 93 | #wasavi_editor.list > div:not([contenteditable])::after { 94 | content:"\21b2"; 95 | font-size:50%; 96 | color:#888; 97 | } 98 | 99 | #wasavi_editor > span.newline { 100 | display:none; 101 | } 102 | 103 | #wasavi_editor > div.current ~ div { 104 | counter-increment:na nr 1; 105 | } 106 | 107 | #wasavi_editor > div:nth-child(4n+3) { 108 | } 109 | 110 | #wasavi_editor > div.current { 111 | } 112 | 113 | #wasavi_editor > div:focus { 114 | outline:none; 115 | } 116 | 117 | #wasavi_editor > div span.wasavi_em { 118 | } 119 | 120 | /* 121 | * line number 122 | */ 123 | 124 | #wasavi_editor.na > div:before, 125 | #wasavi_editor.nr > div:before, 126 | #wasavi_editor.nar > div:before { 127 | display:block; 128 | float:left; 129 | margin:0; 130 | padding:1px /**/1px/**/ 0 0; 131 | text-align:right; 132 | white-space:nowrap; 133 | /**/ 134 | } 135 | 136 | #wasavi_editor.na > div:before { 137 | content:counter(na); 138 | } 139 | 140 | #wasavi_editor.nar > div.current:before { 141 | content:counter(na); 142 | text-align:left; 143 | } 144 | 145 | #wasavi_editor.nr > div:before, 146 | #wasavi_editor.nar > div:before { 147 | content:counter(nr); 148 | } 149 | 150 | /**/ 151 | 152 | /* 153 | * status line 154 | */ 155 | 156 | #wasavi_footer { 157 | } 158 | 159 | /* status line container */ 160 | #wasavi_footer_status_container { 161 | position:fixed; 162 | display:flex; 163 | left:0; right:0; bottom:0; 164 | padding:2px; 165 | box-sizing:border-box; 166 | align-items:stretch; 167 | flex-direction:row; 168 | line-height:1.2; 169 | font-size:10pt; 170 | font-family:/**/monospace/**/; 171 | } 172 | 173 | /* status message indicator */ 174 | #wasavi_footer_file_indicator { 175 | white-space:nowrap; 176 | font-weight:bold; 177 | } 178 | 179 | #wasavi_footer_status_container > #wasavi_footer_file_indicator { 180 | flex-grow:1; 181 | overflow:hidden; 182 | } 183 | 184 | #wasavi_footer_status_container > marquee { 185 | flex-grow:1; 186 | margin:0; 187 | padding:0; 188 | } 189 | 190 | /* prefix input indicator */ 191 | #wasavi_footer_prefix_indicator { 192 | flex-grow:1; 193 | padding:0 0 0 8px; 194 | text-align:right; 195 | white-space:pre; 196 | } 197 | 198 | /* line input */ 199 | #wasavi_footer_input_container { 200 | position:fixed; 201 | display:flex; 202 | left:0; right:0; bottom:0; 203 | padding:2px; 204 | box-sizing:border-box; 205 | align-items:stretch; 206 | flex-direction:row; 207 | line-height:1.2; 208 | font-size:10pt; 209 | font-family:/**/monospace/**/; 210 | } 211 | 212 | #wasavi_footer_input_indicator { 213 | background-color:rgba(0,0,0,0.5); 214 | padding:0 0 0 2px; 215 | color:#fff; 216 | } 217 | 218 | #wasavi_footer_input { 219 | flex-grow:1; 220 | margin:0; 221 | padding:0; 222 | border:none; 223 | outline:none; 224 | background-color:rgba(0,0,0,0.5); 225 | color:#fff; 226 | line-height:1.2; 227 | font-family:/**/monospace/**/; 228 | font-size:10pt; 229 | ime-mode:inactive; 230 | } 231 | 232 | #wasavi_footer_notifier { 233 | visibility:hidden; 234 | position:fixed; 235 | left:8px; 236 | padding:4px; 237 | background-color:rgba(0,0,0,0.75); 238 | color:#fff; 239 | border-radius:3px; 240 | font-size:8pt; 241 | text-shadow:1px 1px #000; 242 | } 243 | 244 | /* 245 | * console 246 | */ 247 | 248 | @keyframes blink { 25% {opacity:0} 75% {opacity:1} } 249 | .blink { 250 | animation:blink 1s step-end infinite; 251 | } 252 | 253 | #wasavi_console_scaler { 254 | position:fixed; 255 | padding:0; 256 | border:none; 257 | font-family:/**/monospace/**/; 258 | font-size:10pt; 259 | left:16px; 260 | top:16px; 261 | right:16px; 262 | white-space:pre-wrap; 263 | overflow-x:auto; 264 | color:#fff; 265 | background-color:#000; 266 | line-height:1; 267 | visibility:hidden; 268 | } 269 | 270 | #wasavi_console_container { 271 | visibility:hidden; 272 | position:fixed; 273 | display:flex; 274 | margin:0; 275 | padding:6px; 276 | left:8px; top:8px; right:8px; bottom:8px; 277 | box-sizing:border-box; 278 | border:none; 279 | border-radius:8px; 280 | flex-direction:row; 281 | justify-content:center; 282 | align-items:stretch; 283 | } 284 | 285 | #wasavi_console { 286 | margin:0; 287 | padding:0; 288 | border:none; 289 | outline:none; 290 | background-color:transparent; 291 | font-family:/**/monospace/**/; 292 | font-size:10pt; 293 | overflow-y:hidden; 294 | white-space:pre-wrap; 295 | resize:none; 296 | line-height:1; 297 | flex-grow:1; 298 | } 299 | 300 | #wasavi_console .special-key { 301 | margin:0 3px 0 3px; 302 | padding:0 1px 0 1px; 303 | border-radius:3px; 304 | } 305 | 306 | /* 307 | * overlay 308 | */ 309 | 310 | #wasavi_cover { 311 | position:fixed; 312 | display:flex; 313 | left:0; top:0; right:0; bottom:0; 314 | background-color:rgba(0,0,0,0.0); 315 | flex-direction:row; 316 | justify-content:center; 317 | align-items:center; 318 | } 319 | 320 | #wasavi_cover.dim { 321 | transition:background-color .5s linear 1s; 322 | background-color:rgba(0,0,0,0.25); 323 | } 324 | 325 | @keyframes visualbell { 326 | 0% { background-color:#f5511e } 327 | 100% { background-color:rgba(0,0,0,0.0) } 328 | } 329 | #wasavi_cover.visualbell { 330 | animation:.5s visualbell 0s; 331 | } 332 | 333 | #wasavi_cover #wasavi_cover_button { 334 | padding:4px; 335 | color:#fff; 336 | background-color:rgba(0,0,0,0.75); 337 | border-radius:6px; 338 | font-family:/**/monospace/**/; 339 | font-size:10pt; 340 | line-height:1; 341 | text-shadow:1px 1px #000; 342 | opacity:0; 343 | } 344 | 345 | #wasavi_cover.dim #wasavi_cover_button { 346 | transition:opacity .5s linear 5s; 347 | opacity:1; 348 | } 349 | 350 | /* 351 | * focus holders 352 | */ 353 | 354 | #wasavi_command_cursor { 355 | position:absolute; 356 | margin:0; 357 | padding:0; 358 | /**/ 359 | text-decoration:none; 360 | text-shadow:none; 361 | left:0px; 362 | top:0px; 363 | } 364 | 365 | #wasavi_command_cursor_inner { 366 | margin:0; 367 | padding:0; 368 | white-space:pre; 369 | } 370 | 371 | #wasavi_focus_holder { 372 | position:fixed; 373 | border:none; 374 | outline:none; 375 | resize:none; 376 | padding:0; 377 | left:0; 378 | top:-32px; 379 | width:100%; 380 | height:32px; 381 | ime-mode:disabled; 382 | } 383 | 384 | #wasavi_fx_clip { 385 | position:fixed; 386 | border:none; 387 | outline:none; 388 | resize:none; 389 | padding:0; 390 | left:0; 391 | top:-64px; 392 | width:100%; 393 | height:32px; 394 | ime-mode:disabled; 395 | } 396 | 397 | /* vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=css fdm=marker : */ 398 | -------------------------------------------------------------------------------- /src/chrome/unicode/fftt_general.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/unicode/fftt_general.dat -------------------------------------------------------------------------------- /src/chrome/unicode/fftt_han_ja.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/unicode/fftt_han_ja.dat -------------------------------------------------------------------------------- /src/chrome/unicode/linebreak.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/chrome/unicode/linebreak.dat -------------------------------------------------------------------------------- /src/chrome/wasavi.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 22 | 23 | 24 | 25 | wasavi - vi editor for any web page 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    46 | 47 | 48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 | 66 |
    67 |
    68 |
    69 | Press ^C to cancel 70 |
    71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/firefox: -------------------------------------------------------------------------------- 1 | chrome -------------------------------------------------------------------------------- /src/mediators/keysnail/wasavi_mediator.ks.js: -------------------------------------------------------------------------------- 1 | let PLUGIN_INFO = 2 | wasavi mediator 3 | Controls suspend state depending on wasavi existence 4 | https://github.com/akahuku/wasavi/raw/master/src/mediators/keysnail/wasavi_mediator.ks.js 5 | https://github.com/akahuku/wasavi/raw/master/src/chrome/images/icon048.png 6 | 1.0.0 7 | 1.8.5 8 | akahuku 9 | Apache License version 2.0 10 | 11 | ; 12 | 13 | /** 14 | * wasavi mediator: ensure running wasavi on environment of Firefox + KeySnail. 15 | * this plugin for KeySnail is a part of wasavi . 16 | * ============================================================================= 17 | * 18 | * @author akahuku@gmail.com 19 | */ 20 | /** 21 | * Copyright 2012-2015 akahuku, akahuku@gmail.com 22 | * 23 | * Licensed under the Apache License, Version 2.0 (the "License"); 24 | * you may not use this file except in compliance with the License. 25 | * You may obtain a copy of the License at 26 | * 27 | * http://www.apache.org/licenses/LICENSE-2.0 28 | * 29 | * Unless required by applicable law or agreed to in writing, software 30 | * distributed under the License is distributed on an "AS IS" BASIS, 31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | * See the License for the specific language governing permissions and 33 | * limitations under the License. 34 | */ 35 | 36 | (function () { 37 | // consts 38 | const OUTPUT_LOG = false; 39 | const TARGET_URL_HTTP = 'http://wasavi.appsweets.net/'; 40 | const TARGET_URL_HTTPS = 'https://ss1.xrea.com/wasavi.appsweets.net/'; 41 | const TARGET_IFRAME = 'body>iframe[src="about:blank?wasavi-frame-source"]'; 42 | const DELAY_MSECS = 250; 43 | 44 | // privates 45 | var delayTimer = null; 46 | var initialized = false; 47 | 48 | function log (message) { 49 | OUTPUT_LOG && console.log('wasavi_mediator: ' + message); 50 | } 51 | 52 | function toBracket (obj) { 53 | return Object.prototype.toString.call(obj); 54 | } 55 | 56 | function getWindow (obj) { 57 | return window.content instanceof Window ? window.content : null; 58 | } 59 | 60 | function isWasaviExist (wnd) { 61 | if (!(wnd.document instanceof HTMLDocument)) return false; 62 | 63 | if (wnd.location.href == TARGET_URL_HTTP 64 | || wnd.location.href == TARGET_URL_HTTPS) { 65 | return true; 66 | } 67 | 68 | return !!wnd.document.querySelector(TARGET_IFRAME); 69 | } 70 | 71 | function registerEvents (wnd) { 72 | if (!(wnd.document instanceof HTMLDocument)) return false; 73 | wnd.document.addEventListener('WasaviStarted', wasaviStarted, false); 74 | wnd.document.addEventListener('WasaviTerminated', wasaviTerminated, false); 75 | return true; 76 | } 77 | 78 | function suspend () { 79 | key.suspended = true; 80 | key.updateStatusDisplay(); 81 | log('keysnail key.suspended set to ' + key.suspended); 82 | } 83 | 84 | function resume () { 85 | key.suspended = false; 86 | key.updateStatusDisplay(); 87 | log('keysnail key.suspended set to ' + key.suspended); 88 | } 89 | 90 | // event handlers 91 | function locationChange () { 92 | delayTimer && clearTimeout(delayTimer); 93 | delayTimer = setTimeout(function () { 94 | delayTimer = null; 95 | var wnd = getWindow(); 96 | if (wnd) { 97 | if (isWasaviExist(wnd)) { 98 | log('keysnail is in suspend state on ' + wnd.location.href); 99 | suspend(); 100 | } 101 | if (registerEvents(wnd)) { 102 | log('registered custom events'); 103 | return; 104 | } 105 | } 106 | log('failed: ' + toBracket(wnd)); 107 | }, DELAY_MSECS); 108 | } 109 | 110 | function wasaviStarted () { 111 | log('catched wasavi launching'); 112 | suspend(); 113 | } 114 | 115 | function wasaviTerminated () { 116 | log('catched wasavi termination'); 117 | resume(); 118 | } 119 | 120 | // 121 | if (!initialized) { 122 | hook.addToHook('LocationChange', locationChange); 123 | initialized = true; 124 | log('registered the wasavi mediator.'); 125 | } 126 | })(); 127 | 128 | // vim:set ts=4 sw=4 fileencoding=UTF-8 fileformat=unix filetype=javascript : 129 | -------------------------------------------------------------------------------- /src/mediators/vimperator/wasavi_mediator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * wasavi mediator: ensure running wasavi on environment of Firefox + vimperator. 3 | * this plugin for vimperator is a part of wasavi . 4 | * ============================================================================= 5 | * 6 | * @author akahuku@gmail.com 7 | */ 8 | /** 9 | * Copyright 2012-2015 akahuku, akahuku@gmail.com 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | (function () { 25 | // consts 26 | var OUTPUT_LOG = false; 27 | var TARGET_URL_HTTP = 'http://wasavi.appsweets.net/'; 28 | var TARGET_URL_HTTPS = 'https://ss1.xrea.com/wasavi.appsweets.net/'; 29 | var TARGET_IFRAME = 'body>iframe[src="about:blank?wasavi-frame-source"]'; 30 | var DELAY_MSECS = 250; 31 | 32 | // privates 33 | var delayTimer = null; 34 | var initialized = false; 35 | 36 | function log (message) { 37 | OUTPUT_LOG && liberator.log('wasavi_mediator: ' + message, 0); 38 | } 39 | 40 | function toBracket (obj) { 41 | return Object.prototype.toString.call(obj); 42 | } 43 | 44 | function getWindow () { 45 | return window.content instanceof Window ? window.content : null; 46 | } 47 | 48 | function isWasaviExists (wnd) { 49 | if (!(wnd.document instanceof HTMLDocument)) return; 50 | 51 | if (wnd.location.href == TARGET_URL_HTTP 52 | || wnd.location.href == TARGET_URL_HTTPS) { 53 | return true; 54 | } 55 | 56 | return !!wnd.document.querySelector(TARGET_IFRAME); 57 | } 58 | 59 | function registerEvents (wnd) { 60 | if (!(wnd.document instanceof HTMLDocument)) return; 61 | wnd.document.addEventListener('WasaviStarted', wasaviStarted, false); 62 | wnd.document.addEventListener('WasaviTerminated', wasaviTerminated, false); 63 | return true; 64 | } 65 | 66 | function suspend () { 67 | liberator.modules.modes.passAllKeys = true; 68 | log('vimp passAllKeys set to ' + liberator.modules.modes.passAllKeys); 69 | } 70 | 71 | function resume () { 72 | liberator.modules.modes.passAllKeys = false; 73 | log('vimp passAllKeys set to ' + liberator.modules.modes.passAllKeys); 74 | } 75 | 76 | // event handlers 77 | function locationChange () { 78 | delayTimer && clearTimeout(delayTimer); 79 | delayTimer = setTimeout(function () { 80 | delayTimer = null; 81 | var wnd = getWindow(); 82 | if (wnd) { 83 | if (isWasaviExists(wnd)) { 84 | log('vimperator is in suspend state on ' + wnd.location.href); 85 | suspend(); 86 | } 87 | if (registerEvents(wnd)) { 88 | log('registered custom events'); 89 | return; 90 | } 91 | } 92 | log('failed: ' + toBracket(wnd)); 93 | }, DELAY_MSECS); 94 | } 95 | 96 | function wasaviStarted (e) { 97 | log('catched wasavi launching'); 98 | suspend(); 99 | } 100 | 101 | function wasaviTerminated (e) { 102 | log('catched wasavi termination'); 103 | resume(); 104 | } 105 | 106 | // 107 | if (!initialized) { 108 | liberator.modules.autocommands.add('LocationChange', /^/, locationChange); 109 | initialized = true; 110 | log('registered the wasavi mediator.'); 111 | } 112 | 113 | })(); 114 | 115 | // vim:set ts=4 sw=4 fileencoding=UTF-8 fileformat=unix filetype=javascript : 116 | -------------------------------------------------------------------------------- /src/opera-blink: -------------------------------------------------------------------------------- 1 | chrome -------------------------------------------------------------------------------- /src/template-chrome.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/template-firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "addons": { 3 | "@appid@": { 4 | "updates": [ 5 | { 6 | "version": "@version@", 7 | "update_link": "@location@" 8 | } 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/template-opera-blink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/unicode-tools/make-fftt-general-dict.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const exec = require('child_process').exec; 6 | const OptionParser = require('brisket/lib/optparse'); 7 | const {fgets, getVersion, awk, pad} = require('./utils.js'); 8 | 9 | const UCD_FILE_NAME = __dirname + '/ucd/UnicodeData.txt'; 10 | const OUT_BIN_FILE = __dirname + '/../chrome/unicode/fftt_general.dat'; 11 | const OUT_HTML_FILE = 'decomp-result.html'; 12 | 13 | const UNICODE_SPACES = [ 14 | // 0x0020, // SPACE 15 | 0x00A0, // NO-BREAK SPACE 16 | 0x1680, // OGHAM SPACE MARK 17 | 0x180E, // MONGOLIAN VOWEL SEPARATOR 18 | 0x2000, // EN QUAD 19 | 0x2001, // EM QUAD 20 | 0x2002, // EN SPACE 21 | 0x2003, // EM SPACE 22 | 0x2004, // THREE-PER-EM SPACE 23 | 0x2005, // FOUR-PER-EM SPACE 24 | 0x2006, // SIX-PER-EM SPACE 25 | 0x2007, // FIGURE SPACE 26 | 0x2008, // PUNCTUATION SPACE 27 | 0x2009, // THIN SPACE 28 | 0x200A, // HAIR SPACE 29 | // 0x200B, // ZERO WIDTH SPACE 30 | 0x202F, // NARROW NO-BREAK SPACE 31 | 0x205F, // MEDIUM MATHEMATICAL SPACE 32 | 0x3000 // IDEOGRAPHIC SPACE 33 | // 0xFEFF // ZERO WIDTH NO-BREAK SPACE 34 | ]; 35 | 36 | const decomposition_data = []; 37 | 38 | var version; 39 | var runmode; 40 | 41 | function setup () { 42 | var count = 0; 43 | fgets(UCD_FILE_NAME, line => { 44 | line = line.split(';'); 45 | if (line[0].length != 4) { 46 | return true; 47 | } 48 | 49 | var cp = parseInt(line[0], 16); 50 | 51 | /* 52 | * override decomposition data with the first letter of alphabetical reading of kana 53 | */ 54 | 55 | // hiragana 56 | if (cp >= 0x3040 && cp <= 0x309f) { 57 | if (/^HIRAGANA LETTER (SMALL )?([A-Z])/.test(line[1])) { 58 | line[5] = pad(RegExp.$2.toLowerCase().charCodeAt(0)); 59 | } 60 | } 61 | // katakana 62 | else if (cp >= 0x30a0 && cp <= 0x30ff) { 63 | if (/^KATAKANA LETTER (SMALL )?([A-Z])/.test(line[1])) { 64 | line[5] = pad(RegExp.$2.toLowerCase().charCodeAt(0)); 65 | } 66 | } 67 | 68 | /* 69 | * store decomposition data 70 | */ 71 | 72 | if (line[5] != '') { 73 | decomposition_data[cp] = line[5].split(' ').map(a => { 74 | if (a.charAt(0) == '<') return a; 75 | return parseInt(a, 16); 76 | }); 77 | } 78 | 79 | count++; 80 | if (count % 100 == 0) { 81 | process.stdout.write(`\r${line[0]}`); 82 | } 83 | }); 84 | 85 | console.log('\n'); 86 | 87 | /* 88 | * another overrides 89 | */ 90 | 91 | // unicode spaces -> U+0020 SPACE 92 | UNICODE_SPACES.forEach(cp => decomposition_data[cp] = [0x20]); 93 | 94 | // U+3001 IDEOGRAPHIC COMMA -> U+002C COMMA 95 | decomposition_data[0x3001] = [0x2C]; 96 | 97 | // U+3002 IDEOGRAPHIC FULL STOP -> U+002E FULL STOP 98 | decomposition_data[0x3002] = [0x2E]; 99 | } 100 | 101 | function get_decomposition_first (cp, nest) { 102 | if (decomposition_data[cp] == undefined) { 103 | return -1; 104 | } 105 | 106 | /* 107 | * recursive sample: ǻ(U+01FB) ; å(U+00E5) ́(U+0301) 108 | * å(U+00E5) ; a(U+0061) ̊(U+030A) 109 | * 110 | * parenthesis sample: ⒜(U+249C) ; ((U+0028) a(U+0061) )(U+0029) 111 | */ 112 | 113 | var decomp = decomposition_data[cp]; 114 | var decomp_tag = ''; 115 | var index = 0; 116 | 117 | // remove decomposition tag, if exists 118 | if (typeof decomp[0] == 'string') { 119 | decomp_tag = decomp.shift(); 120 | } 121 | 122 | // if decomposition is surrounded by parenthesis, remove paren 123 | if (decomp.length >= 2 && decomp[0] == 0x0028 && decomp[decomp.length - 1] == 0x0029) { 124 | decomp.shift(); 125 | decomp.pop();; 126 | } 127 | 128 | // make recursion resolved array 129 | for (var i = 0, goal = decomp.length; i < goal; i++) { 130 | var result = get_decomposition_first(decomp[i], nest + 1); 131 | if (result == -1) { 132 | result = decomp[i]; 133 | } 134 | if (result == 0x0020 && UNICODE_SPACES.indexOf(cp) >= 0) { 135 | return result; 136 | } 137 | if (result >= 0x0021 && result <= 0x007e) { 138 | return cp == result ? -1 : result; 139 | } 140 | } 141 | 142 | return -1; 143 | } 144 | 145 | function make_dict (is_html = false) { 146 | var file_name; 147 | var header; 148 | 149 | if (is_html) { 150 | file_name = OUT_HTML_FILE; 151 | header = ` 152 | Latin-1 alternative presentation in \`f\` \`F\` \`t\` \`T\` command 153 | ================== 154 | Unicode version: ${version} 155 | 156 | _Original Codepoint_ | _Latin-1 Alternative presentation_ 157 | -------------------- | ---------------------------------- 158 | `; 159 | } 160 | else { 161 | file_name = OUT_BIN_FILE; 162 | header = ''; 163 | } 164 | 165 | var count = 0; 166 | var buffer = header; 167 | console.log(header); 168 | for (var cp = 0; cp < 0x10000; cp++) { 169 | var result = get_decomposition_first(cp, 0); 170 | if (result >= 0) { 171 | if (is_html) { 172 | console.log( 173 | `&#x${pad(cp, ' ', 4)}; (U+${pad(cp)})` + 174 | ` | \`${String.fromCharCode(result)}\` (U+${pad(result)})`); 175 | } 176 | else { 177 | buffer += String.fromCharCode((cp ) & 0xff); 178 | buffer += String.fromCharCode((cp >> 8) & 0xff); 179 | buffer += String.fromCharCode(result); 180 | 181 | if (count % 100 == 0) { 182 | process.stdout.write(`\r${pad(cp)}`); 183 | } 184 | } 185 | count++; 186 | } 187 | 188 | } 189 | 190 | if (is_html) { 191 | console.log(`\nTotal number of mappings: ${count}`); 192 | } 193 | else { 194 | fs.writeFileSync(file_name, buffer, {encoding:'binary', mode:0o664}); 195 | console.log(`\n${count} codepoints are generated into ${file_name}.`); 196 | } 197 | 198 | } 199 | 200 | function printHelp () { 201 | console.log('usage: --html Output html document'); 202 | console.log(' --binary Output binary data file'); 203 | process.exit(1); 204 | } 205 | 206 | (new OptionParser) 207 | .on('--html Output html document', v => { 208 | runmode = 'html'; 209 | }) 210 | .on('--binary Output binary data file', v => { 211 | runmode = 'binary'; 212 | }) 213 | .parse(process.argv); 214 | 215 | getVersion() 216 | .then(() => { 217 | switch (runmode) { 218 | case 'html': 219 | setup(); 220 | make_dict(true); 221 | break; 222 | case 'binary': 223 | setup(); 224 | make_dict(); 225 | break; 226 | default: 227 | printHelp(); 228 | break; 229 | } 230 | }) 231 | .catch(error => console.error(error)); 232 | -------------------------------------------------------------------------------- /src/unicode-tools/make-fftt-hanja-dict.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const exec = require('child_process').exec; 6 | const OptionParser = require('brisket/lib/optparse'); 7 | const {fgets, getVersion, awk, pad, ensureFileExists} = require('./utils.js'); 8 | 9 | const file_name = __dirname + '/unihan/Unihan_Readings.txt'; 10 | const out_file = __dirname + '/../chrome/unicode/fftt_han_ja.dat'; 11 | 12 | const data = []; 13 | var version; 14 | var runmode = {}; 15 | 16 | function setup () { 17 | var count = 0; 18 | var bits = []; 19 | 20 | for (var i = 65; i <= 90; i++) { 21 | bits[i] = 0; 22 | } 23 | 24 | fgets(file_name, line => { 25 | line = line.split('\t'); 26 | if (line.length != 3) return; 27 | if (line[1] != 'kJapaneseOn' && line[1] != 'kJapaneseKun') return; 28 | if (line[0].length != 6) return true; 29 | 30 | var code_point = parseInt(line[0].substring(2, 6), 16); 31 | if (data[code_point] == undefined) { 32 | data[code_point] = 0; 33 | } 34 | 35 | line[2].split(' ').some(r => { 36 | r = r.charCodeAt(0); 37 | if (r < 65 || r > 90) { 38 | console.log(`\n*** invalid code point: ${line.join('\t')}`); 39 | return true; 40 | } 41 | 42 | data[code_point] |= 1 << (r - 65); 43 | bits[r]++; 44 | }); 45 | 46 | count++; 47 | if (count % 100 == 0) { 48 | process.stdout.write(`\r${count}`); 49 | } 50 | }); 51 | } 52 | 53 | function make_dict () { 54 | var packmap = {}; 55 | var bits = 0; 56 | var codes = []; 57 | 58 | for (var i = 0; i < 26; i++) { 59 | packmap[`${i}_`] = i; 60 | } 61 | 62 | data.forEach((d, cp) => { 63 | bits |= d; 64 | }); 65 | 66 | for (var i = 0, mask = 1; i < 26; i++, mask <<= 1) { 67 | if (!(bits & mask)) { 68 | delete packmap[`${i}_`]; 69 | codes.push(`\t//omitted: '${String.fromCharCode(97 + i)}'`); 70 | } 71 | } 72 | 73 | if (Math.floor((packmap.length + 7) / 8) > 3) { 74 | throw new Error('data size too large.'); 75 | } 76 | 77 | var mask = 1; 78 | for (var k in packmap) { 79 | packmap[k] = mask; 80 | mask <<= 1; 81 | } 82 | 83 | /* javascript code */ 84 | 85 | if (runmode.packmap) { 86 | var delimiter = ' '; 87 | for (var k in packmap) { 88 | var v = packmap[k]; 89 | codes.push(`\t${delimiter}0x${pad(v, '0', 6)}: '${String.fromCharCode(97 + parseInt(k, 10))}'`); 90 | delimiter = ','; 91 | } 92 | codes.sort((a, b) => a.substr(-2).localeCompare(b.substr(-2))); 93 | 94 | console.log(`\r// packmap info derived from Unicode ${version}`); 95 | console.log(`// generated by "src/unicode-tools/${path.basename(__filename)} --packmap"`); 96 | console.log(`const packmap = {`); 97 | console.log(codes.join('\n')); 98 | console.log(`};`); 99 | } 100 | 101 | if (runmode.binary) { 102 | var buffer = ''; 103 | data.forEach((d, cp) => { 104 | var dd = 0; 105 | for (var i = 0, mask = 1, goal = Object.keys(packmap).length; i <= goal; i++, mask <<= 1) { 106 | if (d & mask) { 107 | dd |= packmap[`${i}_`]; 108 | } 109 | } 110 | buffer += String.fromCharCode((cp ) & 0xff); 111 | buffer += String.fromCharCode((cp >> 8) & 0xff); 112 | buffer += String.fromCharCode((dd ) & 0xff); 113 | buffer += String.fromCharCode((dd >> 8) & 0xff); 114 | buffer += String.fromCharCode((dd >> 16) & 0xff); 115 | }); 116 | 117 | fs.writeFileSync(out_file, buffer, {encoding:'binary', mode:0o664}); 118 | console.log(`\ndone.`); 119 | } 120 | } 121 | 122 | function printHelp () { 123 | console.log('runmode not specified. set one of the following switches:'); 124 | console.log(' --packmap Output javascript code which defines packmap to stdout'); 125 | console.log(' --binary Output data file to src/chrome/unicode/fftt_han_ja.dat'); 126 | process.exit(1); 127 | } 128 | 129 | (new OptionParser) 130 | .on('--packmap Output javascript code which defines packmap to stdout', v => { 131 | runmode.packmap = true; 132 | }) 133 | .on('--binary Output data file to src/chrome/unicode/fftt_han_ja.dat', v => { 134 | runmode.binary = true; 135 | }) 136 | .parse(process.argv); 137 | 138 | getVersion() 139 | .then(ver => version = ver) 140 | .then(() => { 141 | if (!runmode.packmap && !runmode.binary) { 142 | printHelp(); 143 | } 144 | else { 145 | setup(); 146 | make_dict(); 147 | } 148 | }) 149 | .catch(error => console.error(error)); 150 | 151 | -------------------------------------------------------------------------------- /src/unicode-tools/make-linebreak-dict.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const exec = require('child_process').exec; 6 | const OptionParser = require('brisket/lib/optparse'); 7 | const {fgets, getVersion, awk, pad, ensureFileExists} = require('./utils.js'); 8 | 9 | const file_name = __dirname + '/ucd/LineBreak.txt'; 10 | const out_file = __dirname + '/../chrome/unicode/linebreak.dat'; 11 | 12 | const data = []; 13 | const props = { 14 | 'OP': 0, // Open Punctuation (XA) 15 | 'CL': 1, // Close Punctuation (XB) 16 | 'CP': 2, // Closing Parenthesis (XB) 17 | 'QU': 3, // Quotation (XB/XA) 18 | 'GL': 4, // Non-breaking ('Glue') (XB/XA) (Non-tailorable) 19 | 'NS': 5, // Nonstarters (XB) 20 | 'EX': 6, // Exclamation/Interrogation (XB) 21 | 'SY': 7, // Symbols Allowing Break After (A) 22 | 'IS': 8, // Infix Numeric Separator (XB) 23 | 'PR': 9, // Prefix Numeric (XA) 24 | 'PO': 10, // Postfix Numeric (XB) , 25 | 'NU': 11, // Numeric (XP) , 26 | 'AL': 12, // Ordinaty Alphabetic and Symbol characters (XP), 27 | 'HL': 13, // Hebrew Letter (XB) , 28 | 'ID': 14, // Ideographic (B/A) , 29 | 'IN': 15, // Inseparable Characters (XP) , 30 | 'HY': 16, // Hyphen (XA) , 31 | 'BA': 17, // Break After (A) , 32 | 'BB': 18, // Break Before (B) , 33 | 'B2': 19, // Break Oppotunity Before and After (B/A/XP) , 34 | 'ZW': 20, // Zero Width Space (A) (Non-tailorable) , 35 | 'CM': 21, // Combining Mark (XB) (Non-tailorable) , 36 | 'WJ': 22, // Word Joiner (XB/XA) (Non-tailorable) , 37 | 'H2': 23, // Hangul LV Syllable (B/A) , 38 | 'H3': 24, // Hangul LVT syllable (B/A) , 39 | 'JL': 25, // Hangul L Jamo (B) , 40 | 'JV': 26, // Hangul V Jamo (XA/XB) , 41 | 'JT': 27, // Hangul T Jamo (A) , 42 | 'RI': 28, // Regional Indicator (B/A/XP) , 43 | 'EB': 29, // Emoji Base (B/A) 44 | 'EM': 30, // Emoji Modifier (A) 45 | 'ZWJ': 31, // Zero Width Joiner (XA/XB) (Non-tailorable) 46 | 47 | 'BK': 245, // Mandatory Break (A) (Non-tailorable) 48 | 'CR': 246, // Carriage Return (A) (Non-tailorable) 49 | 'LF': 247, // Line Feed (A) (Non-tailorable) 50 | 'NL': 248, // Next Line (A) (Non-tailorable) 51 | 'SG': 249, // Surrogate (XP) (Non-tailorable) 52 | 'SP': 250, // Space (A) (Non-tailorable) 53 | 'CB': 251, // Contingent Break Oppotunity (B/A) 54 | 'AI': 252, // Ambiguous (Alphabetic or Ideograph) 55 | 'CJ': 253, // Conditional Japanese Starter 56 | 'SA': 254, // Complex-Context Dependent (South East Asian) (P) 57 | 'XX': 255 // Unknown (XP) 58 | }; 59 | var version; 60 | var runmode; 61 | 62 | function setup () { 63 | ensureFileExists(file_name); 64 | 65 | console.log('reading...'); 66 | 67 | var content = fs.readFileSync(file_name, 'utf8').split('\n'); 68 | var count = 0; 69 | var start_cp = 0; 70 | var start_prop = ''; 71 | var prev_cp = 0; 72 | 73 | for (var i = 0, goal = content.length; i < goal; i++) { 74 | var line = content[i]; 75 | var comment_index = line.indexOf('#'); 76 | if (comment_index >= 0) { 77 | line = line.substring(0, comment_index); 78 | } 79 | 80 | line = line.replace(/\s+$/, ''); 81 | if (line == '') continue; 82 | 83 | line = line.split(';'); line = [line[0], line.slice(1).join(';')]; 84 | var cp = parseInt(line[0], 16); 85 | var prop = line[1].split('#'); 86 | prop = prop[0].replace(/^\s+|\s+$/g, ''); 87 | 88 | if (!(prop in props)) { 89 | console.log(`\rUnknown prop: "${prop}" in line ${count + 1}: ${line.join(' ')}`); 90 | process.exit(1); 91 | } 92 | if (prop != start_prop) { 93 | if (start_prop != '') { 94 | data.push([start_cp, prev_cp, props[start_prop]]); 95 | } 96 | 97 | start_cp = cp; 98 | start_prop = prop; 99 | } 100 | 101 | if (line[0].indexOf('..') >= 0) { 102 | var range = line[0].split('..'); 103 | data.push([parseInt(range[0], 16), parseInt(range[1], 16), props[prop]]); 104 | start_cp = 0; 105 | start_prop = ''; 106 | } 107 | 108 | count++; 109 | prev_cp = cp; 110 | if (count % 100 == 0) { 111 | process.stdout.write(`\r${line[0]}`); 112 | } 113 | } 114 | 115 | if (start_prop != '') { 116 | data.push([start_cp, cp, props[start_prop]]); 117 | } 118 | 119 | console.log(`\rdone: ${data.length} items.\n`); 120 | } 121 | 122 | function make_dict () { 123 | console.log('generating data...'); 124 | var count = 0; 125 | var index = 0; 126 | var buffer = ''; 127 | data.forEach(d => { 128 | buffer += String.fromCharCode((d[0] ) & 0xff); 129 | buffer += String.fromCharCode((d[0] >> 8) & 0xff); 130 | buffer += String.fromCharCode((d[0] >> 16) & 0xff); 131 | buffer += String.fromCharCode((d[1] ) & 0xff); 132 | buffer += String.fromCharCode((d[1] >> 8) & 0xff); 133 | buffer += String.fromCharCode((d[1] >> 16) & 0xff); 134 | buffer += String.fromCharCode(d[2]); 135 | 136 | count++; 137 | if (count % 100 == 0) { 138 | process.stdout.write(`\r${Math.floor(count / data.length * 100)}%`); 139 | } 140 | }); 141 | 142 | fs.writeFileSync(out_file, buffer, {encoding:'binary', mode:0o664}); 143 | console.log(`\rdone: ${count} items.`); 144 | } 145 | 146 | function make_js () { 147 | console.log(`// line break property in Unicode ${version}`); 148 | console.log(`// generated by "src/unicode-tool/${path.basename(__filename)} --js"`); 149 | console.log(`const BREAK_PROP = {`); 150 | var delimiter = ','; 151 | for (var prop in props) { 152 | var offset = props[prop]; 153 | if (prop == 'XX') { 154 | delimiter = ''; 155 | } 156 | console.log(`\t${prop}: ${(' ' + offset).substr(-3)}${delimiter}`); 157 | } 158 | console.log('};'); 159 | } 160 | 161 | function printHelp () { 162 | console.log('usage: --dict Generate line-break data file'); 163 | console.log(' --js Generate javascript code of line-break property definition'); 164 | process.exit(1); 165 | } 166 | 167 | (new OptionParser) 168 | .on('--dict Generate line-break data file', v => { 169 | runmode = 'dict'; 170 | }) 171 | .on('--js Generate javascript code of line-break property definition', v => { 172 | runmode = 'js'; 173 | }) 174 | .parse(process.argv); 175 | 176 | getVersion() 177 | .then(() => { 178 | switch (runmode) { 179 | case 'dict': 180 | setup(); 181 | make_dict(); 182 | break; 183 | case 'js': 184 | setup(); 185 | make_js(); 186 | break; 187 | default: 188 | printHelp(); 189 | break; 190 | } 191 | }) 192 | .catch(error => console.error(error)); 193 | -------------------------------------------------------------------------------- /src/unicode-tools/make-prop-regex.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const exec = require('child_process').exec; 6 | const OptionParser = require('brisket/lib/optparse'); 7 | const {fgets, getVersion, awk, pad} = require('./utils.js'); 8 | 9 | var category; 10 | var var_suffix; 11 | var file; 12 | var filter_prefix; 13 | var filter_suffix; 14 | var version; 15 | 16 | function loadUnicodeData () { 17 | var content = awk( 18 | `${__dirname}/ucd/${file}`, 19 | new RegExp(`${filter_prefix}${category}${filter_suffix}`), 20 | (line, re) => line.split(';')[0] 21 | ); 22 | return content.split('\n'); 23 | } 24 | 25 | function generate (lines) { 26 | var buf = []; 27 | var initial = ''; 28 | var tmp = initial; 29 | 30 | buf.push(`// ${category} letters in ${file} of Unicode ${version}`); 31 | buf.push(`// generated by "src/unicode-tools/${path.basename(__filename)} ${process.argv[2]}"`); 32 | buf.push(`const REGEX_${var_suffix.toUpperCase()} = new RegExp('[\\`); 33 | 34 | if (category == 'Zs') { 35 | lines.unshift('0009'); 36 | } 37 | 38 | for (var i = 0; i < lines.length; i++) { 39 | var cp = lines[i].split('..').map(s => s.replace(/^\s+|\s+$/g, ''));; 40 | if (cp[0].length > 4) continue; 41 | if (cp.length == 2 && parseInt(cp[0], 16) + 1 == parseInt(cp[1], 16)) { 42 | lines.splice(i + 1, 0, cp.pop()); 43 | } 44 | var fragment = '\\\\u' + cp.join('-\\\\u'); 45 | if ((tmp + fragment + '\\').length >= 80) { 46 | buf.push(tmp + '\\'); 47 | tmp = initial + fragment; 48 | } 49 | else { 50 | tmp += fragment; 51 | } 52 | } 53 | if (tmp != initial) { 54 | buf.push(tmp + '\\'); 55 | } 56 | buf.push("]');"); 57 | buf.push(''); 58 | 59 | buf = buf.map((line, i) => line.replace(/ /g, '\t')); 60 | console.log(buf.join('\n')); 61 | } 62 | 63 | function printHelp () { 64 | if (file == undefined) { 65 | console.log('usage: --general-category='); 66 | console.log(' --prop-list='); 67 | process.exit(1); 68 | } 69 | } 70 | 71 | (new OptionParser) 72 | .on('--general-category Unicode general category name', v => { 73 | category = v; 74 | var_suffix = v; 75 | file = 'UnicodeData.txt'; 76 | filter_prefix = filter_suffix = ';'; 77 | }) 78 | .on('--prop-list Unicode prop list name', v => { 79 | category = v; 80 | var_suffix = v; 81 | file = 'PropList.txt'; 82 | filter_prefix = '; '; 83 | filter_suffix = ' #'; 84 | if (category == 'Sentence_Terminal') { 85 | var_suffix = 'STerm'; 86 | } 87 | else if (category == 'Terminal_Punctuation') { 88 | var_suffix = 'PTerm'; 89 | } 90 | }) 91 | .parse(process.argv); 92 | 93 | getVersion() 94 | .then(ver => version = ver) 95 | .then(printHelp) 96 | .then(loadUnicodeData) 97 | .then(generate) 98 | .catch(error => console.error(error)); 99 | -------------------------------------------------------------------------------- /src/unicode-tools/make-scripts.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const exec = require('child_process').exec; 6 | const OptionParser = require('brisket/lib/optparse'); 7 | const {fgets, getVersion, awk, pad, toUTF16, ensureFileExists} = require('./utils.js'); 8 | 9 | const file_name = __dirname + '/ucd/Scripts.txt'; 10 | const data = []; 11 | var file_version = false; 12 | var runmode; 13 | 14 | const han_table = { 15 | 'Chinese': [ 16 | [0x2E80, 0x2EFF, 'CJK Radicals Supplement'], 17 | [0x2F00, 0x2FDF, 'Kangxi Radicals'], 18 | [0x3100, 0x312F, 'Bopomofo'], 19 | [0x31A0, 0x31BF, 'Bopomofo Extended'], 20 | [0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'], 21 | [0x4E00, 0x9FCF, 'CJK Unified Ideographs (Han)'], 22 | [0xF900, 0xFAFF, 'CJK Compatibility Ideographs'], 23 | [0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'], 24 | [0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'], 25 | [0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'], 26 | [0x2B820, 0x2CEAF, 'CJK Unified Ideographs Extension E'], 27 | [0x2F800, 0x2FA1F, 'CJK Compatibility Ideographs Supplement'] 28 | ], 29 | 'Japanese': [ 30 | [0x3040, 0x309F, 'Hiragana'], 31 | [0x30A0, 0x30FF, 'Katakana'], 32 | [0x3190, 0x319F, 'Kanbun'], 33 | [0x31F0, 0x31FF, 'Katakana Phonetic Extensions'], 34 | [0xFF65, 0xFF9F, 'Halfwidth Katakana'], 35 | [0x1B000, 0x1B0FF, 'Kana Supplement'] 36 | ], 37 | 'Korean': [ 38 | [0x1100, 0x11FF, 'Hangul Jamo'], 39 | [0x3130, 0x318F, 'Hangul Compatibility Jamo'], 40 | [0xA960, 0xA97F, 'Hangul Jamo Extended-A'], 41 | [0xAC00, 0xD7AF, 'Hangul Syllables'], 42 | [0xD7B0, 0xD7FF, 'Hangul Jamo Extended-B'], 43 | [0xFFA0, 0xFFDC, 'Halfwidth Jamo'] 44 | ], 45 | 'Yi': [ 46 | [0xA000, 0xA48F, 'Yi Syllables'], 47 | [0xA490, 0xA4CF, 'Yi Radicals'] 48 | ] 49 | }; 50 | 51 | function setup () { 52 | ensureFileExists(file_name); 53 | 54 | var content = fs.readFileSync(file_name, 'utf8').split('\n'); 55 | var count = 0; 56 | for (var i = 0, goal = content.length; i < goal; i++) { 57 | var line = content[i].replace(/\s+$/, ''); 58 | var re; 59 | if (line == '') continue; 60 | if (/^\s*#/.test(line)) { 61 | if (file_version === false && (re = /Scripts-([0-9.]+)\.txt/.exec(line))) { 62 | file_version = re[1]; 63 | } 64 | continue; 65 | } 66 | 67 | re = /^([0-9A-F]+)(\.\.([0-9A-F]+))?\s*;\s*([^ #]+)\s*#\s*(..)/.exec(line); 68 | if (re) { 69 | var low = parseInt(re[1], 16); 70 | var high = re[2] == undefined ? low : parseInt(re[3], 16); 71 | var script_name = re[4]; 72 | var prop_val = re[5]; 73 | 74 | if (low >= 0x10000) continue; 75 | } 76 | 77 | for (var j = low; j <= high; j++) { 78 | data[j] = [script_name, prop_val]; 79 | } 80 | 81 | count++; 82 | } 83 | } 84 | 85 | function output_han_table () { 86 | var littleRanges = []; 87 | var bigRanges = []; 88 | var count = 0; 89 | 90 | for (var script in han_table) { 91 | var langOutput = false; 92 | han_table[script].forEach(d => { 93 | if (d[0] >= 0x10000) { 94 | bigRanges.push(`\t/* ${script}, ${d[2]} (U+${pad(d[0])} - U+${pad(d[1])}) */`); 95 | bigRanges.push(`\t'${getCharacterClassFromSMP(d[0], d[1])}',`); 96 | } 97 | else { 98 | if (!langOutput) { 99 | if (count) { 100 | littleRanges.push(''); 101 | } 102 | littleRanges.push(`\t/* ${script} */`); 103 | langOutput = true; 104 | } 105 | littleRanges.push(`\t'\\\\u${pad(d[0])}-\\\\u${pad(d[1])}',\t\t// ${d[2]}`); 106 | } 107 | }); 108 | count++; 109 | } 110 | 111 | littleRanges[littleRanges.length - 1] = littleRanges[littleRanges.length - 1] 112 | .replace(/,/, ''); 113 | bigRanges[bigRanges.length - 1] = bigRanges[bigRanges.length - 1] 114 | .replace(/,/, ''); 115 | 116 | console.log(`// Ideographic letters in Scripts.txt of Unicode ${file_version}`); 117 | console.log(`// generated by "src/uncode-tools/${path.basename(__filename)} --han"`); 118 | console.log(`const REGEX_HAN_FAMILY = new RegExp('[' + [`); 119 | console.log(littleRanges.join('\n')); 120 | console.log(`].join('') + ']|' + [`); 121 | console.log(bigRanges.join('\n')); 122 | console.log(`].join('|'));`); 123 | console.log(''); 124 | } 125 | 126 | function output_nonletters (include, exclude, var_name) { 127 | var nonletters = []; 128 | var last = -1; 129 | data.forEach((d, cp) => { 130 | if (include && include.test(d[1])) return; 131 | if (exclude && exclude.test(d[1])) return; 132 | 133 | if (last < 0) { 134 | nonletters.push([d[0], d[1], cp, cp]); 135 | last = nonletters.length - 1; 136 | } 137 | else { 138 | if (cp == nonletters[last][3] + 1) { 139 | nonletters[last][3] = cp; 140 | } 141 | else { 142 | nonletters.push([d[0], d[1], cp, cp]); 143 | last = nonletters.length - 1; 144 | } 145 | } 146 | }); 147 | 148 | var codes = []; 149 | if (include) { 150 | codes.push(`// ${include.source} of General Category in Scripts.txt of Unicode ${file_version}`); 151 | } 152 | else if (exclude) { 153 | codes.push(`// not(${exclude}) of General Category in Scripts.txt of Unicode ${file_version}`); 154 | } 155 | codes.push(`// generated by "src/unicode-tools/${path.basename(__filename)} --nonletters"`); 156 | codes.push(`const ${var_name} = new RegExp('[\\`); 157 | var code = ''; 158 | nonletters.forEach(d => { 159 | switch (d[3] - d[2]) { 160 | case 0: 161 | code += '\\\\u' + pad(d[2]); 162 | break; 163 | case 1: 164 | code += '\\\\u' + pad(d[2]) + 165 | '\\\\u' + pad(d[3]); 166 | break; 167 | default: 168 | code += '\\\\u' + pad(d[2]) + '-' + 169 | '\\\\u' + pad(d[3]); 170 | } 171 | if (code.length >= 90) { 172 | codes.push(code + '\\'); 173 | code = ''; 174 | } 175 | }); 176 | if (code != '') { 177 | codes.push(code + '\\'); 178 | } 179 | codes.push("]');"); 180 | console.log(codes.join('\n')); 181 | console.log(''); 182 | } 183 | 184 | function output_scripts () { 185 | var scripts = []; 186 | var script_ids = {}; 187 | var last = -1; 188 | data.forEach((d, cp) => { 189 | if (last < 0) { 190 | scripts.push([d[0], d[1], cp, cp]); 191 | last = scripts.length - 1; 192 | } 193 | else { 194 | if (d[0] == scripts[last][0]) { 195 | scripts[last][3] = cp; 196 | } 197 | else { 198 | scripts.push([d[0], d[1], cp, cp]); 199 | last = scripts.length - 1; 200 | } 201 | } 202 | if (!(d[0] in script_ids)) { 203 | script_ids[d[0]] = Object.keys(script_ids).length; 204 | } 205 | }); 206 | 207 | var codes = []; 208 | codes.push(`// Scripts of Unicode ${file_version}`); 209 | codes.push(`const SCRIPT_TABLE = {\\`); 210 | scripts.forEach(d => { 211 | if (d[0] == 'Common') return; 212 | codes.push( 213 | ' ' + 214 | '0x' + pad(d[2]) + ', ' + 215 | '0x' + pad(d[3]) + ', ' + 216 | pad(script_ids[d[0]], ' ', 3) + ', ' + 217 | '// ' + d[0]); 218 | }); 219 | codes[codes.length - 1] = codes[codes.length - 1].replace(/,( \/\/)/, ' $1'); 220 | codes.push('};'); 221 | console.log(codes.join('\n')); 222 | console.log(''); 223 | console.log(''); 224 | } 225 | 226 | function getCharacterClassFromSMP (from, to) { 227 | var data = [/*high-start, high-end, low-start, low-end*/]; 228 | for (var i = from; i <= to; i++) { 229 | var pair = toUTF16(i); 230 | if (data.length && data[data.length - 1][0] == pair[0]) { 231 | data[data.length - 1][3] = pair[1]; 232 | } 233 | else { 234 | data.push([pair[0], pair[0], pair[1], pair[1]]); 235 | } 236 | } 237 | 238 | for (var i = 0; i < data.length - 1; i++) { 239 | if (data[i][1] + 1 == data[i + 1][0] 240 | && data[i][2] == data[i + 1][2] 241 | && data[i][3] == data[i + 1][3]) { 242 | data[i][1] = data[i + 1][0]; 243 | data.splice(i + 1, 1); 244 | i--; 245 | } 246 | } 247 | 248 | var result = []; 249 | 250 | data.forEach(d => { 251 | var highFirst = d[0]; 252 | var highLast = d[1]; 253 | var lowFirst = d[2]; 254 | var lowLast = d[3]; 255 | if (highFirst != highLast) { 256 | if (lowFirst != lowLast) { 257 | result.push( 258 | `[\\\\u${pad(highFirst)}-\\\\u${pad(highLast)}]` + 259 | `[\\\\u${pad(lowFirst)}-\\\\u${pad(lowLast)}]` 260 | ); 261 | } 262 | else { 263 | result.push( 264 | `[\\\\u${pad(highFirst)}-\\\\u${pad(highLast)}]` + 265 | `\\\\u${pad(lowFirst)}` 266 | ); 267 | } 268 | } 269 | else { 270 | if (lowFirst != lowLast) { 271 | result.push( 272 | `\\\\u${pad(highFirst)}[\\\\u${pad(lowFirst)}-\\\\u${pad(lowLast)}]` 273 | ); 274 | } 275 | else { 276 | result.push( 277 | `\\\\u${pad(highFirst)}\\\\u${pad(lowFirst)}` 278 | ); 279 | } 280 | } 281 | }); 282 | 283 | return result.join('|'); 284 | } 285 | 286 | function printHelp () { 287 | console.log('usage: --han Generate CJK Ideograph definition code'); 288 | console.log(' --nonletters Generate non-letters definition code'); 289 | process.exit(1); 290 | } 291 | 292 | (new OptionParser) 293 | .on('--han Generate CJK Ideograph definition code', v => { 294 | runmode = 'han'; 295 | }) 296 | .on('--nonletters Generate non-letters definition code', v => { 297 | runmode = 'nonletters'; 298 | }) 299 | .parse(process.argv); 300 | 301 | getVersion() 302 | .then(() => { 303 | switch (runmode) { 304 | case 'han': 305 | setup(); 306 | output_han_table(); 307 | break; 308 | case 'nonletters': 309 | setup(); 310 | output_nonletters(null, /[ZLN]./, 'REGEX_NON_LETTER'); 311 | break; 312 | default: 313 | printHelp(); 314 | break; 315 | } 316 | }) 317 | .catch(error => console.error(error)); 318 | -------------------------------------------------------------------------------- /src/unicode-tools/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | function fgets (file, callback) { 4 | const BUFFER_SIZE = 8192; 5 | 6 | ensureFileExists(file); 7 | 8 | var buffer = new Buffer(BUFFER_SIZE); 9 | var index = 0; 10 | var lines = []; 11 | var fd = fs.openSync(file, 'r'); 12 | 13 | try { 14 | mainloop: 15 | while (true) { 16 | var readBytes = fs.readSync(fd, buffer, 0, BUFFER_SIZE, index); 17 | if (readBytes == 0) { 18 | break; 19 | } 20 | var lines = buffer.slice(0, readBytes).toString().split('\n'); 21 | var backBytes = 0; 22 | if (lines.length > 1) { 23 | backBytes = lines.pop().length; 24 | } 25 | 26 | index += readBytes - backBytes; 27 | 28 | while (lines.length) { 29 | if (callback(lines.shift()) === true) { 30 | break mainloop; 31 | } 32 | } 33 | } 34 | } 35 | finally { 36 | fs.closeSync(fd); 37 | } 38 | } 39 | 40 | function ensureFileExists (file) { 41 | if (!fs.existsSync(file)) { 42 | throw new Error( 43 | `error: "${filename}" is not exist.\n` + 44 | ` please download any version of UCD.zip from http://www.unicode.org/Public/zipped/\n` + 45 | ` and extract into ${__dirname}/ucd/` 46 | ); 47 | } 48 | } 49 | 50 | function getVersion () { 51 | return new Promise(function (resolve, reject) { 52 | var filename = `${__dirname}/ucd/ReadMe.txt`; 53 | ensureFileExists(filename); 54 | var version = awk(filename, /[0-9]+\.[0-9]+\.[0-9]/, (line, re) => re[0]); 55 | resolve(version.split('\n')[0]); 56 | }); 57 | } 58 | 59 | function awk (file, pattern, handler) { 60 | if (!fs.existsSync(file)) { 61 | throw new Error( 62 | `error: "${file}" is not exist.\n` + 63 | ` please download any version of UCD.zip from http://www.unicode.org/Public/zipped/\n` + 64 | ` and extract into ${__dirname}/ucd/`); 65 | } 66 | 67 | var content = fs.readFileSync(file, 'utf8').split('\n'); 68 | var result = []; 69 | var re; 70 | for (var i = 0, goal = content.length; i < goal; i++) { 71 | re = pattern.exec(content[i]); 72 | if (re) { 73 | result.push(handler(content[i], re)); 74 | } 75 | } 76 | return result.join('\n'); 77 | } 78 | 79 | function pad (s, p, w) { 80 | p || (p = '0'); 81 | if (typeof s == 'string') { 82 | s = s.charCodeAt(0); 83 | } 84 | var leader = '0000'; 85 | if (w == undefined) { 86 | w = 4; 87 | } 88 | if (p != '0' || w != leader.length) { 89 | leader = ''; 90 | for (var i = 0; i < w; i++) { 91 | leader += p; 92 | } 93 | } 94 | var result = s.toString(16).toUpperCase(); 95 | if (result.length < w) { 96 | result = (leader + result).substr(-w); 97 | } 98 | return result; 99 | } 100 | 101 | function toUTF16 (cp) { 102 | var p = (cp & 0x1f0000) >> 16; 103 | var o = cp & 0xffff; 104 | return p ? 105 | [ 106 | 0xd800 | ((p - 1) << 6) | ((o & 0xfc00) >> 10), 107 | 0xdc00 | (o & 0x03ff) 108 | ] : 109 | o; 110 | } 111 | 112 | exports.fgets = fgets; 113 | exports.getVersion = getVersion; 114 | exports.ensureFileExists = ensureFileExists; 115 | exports.awk = awk; 116 | exports.pad = pad; 117 | exports.toUTF16 = toUTF16; 118 | -------------------------------------------------------------------------------- /src/unit-tests/IncDec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | require('../chrome/frontend/classes.js'); 8 | 9 | // 0 1 2 3 4 10 | // 0123456789012345678901234567890123456789012 11 | const target = 'abc z 0b000345 0x0fghi 00789 -1234 5678 -09'; 12 | 13 | describe('class IncDec', function () { 14 | it('should extract decimal numbers', function () { 15 | var incdec = new Wasavi.IncDec; 16 | var a = incdec.extractTargets(target, 6, {formats: ''}); 17 | var whole = a.map(item => item.text).join(','); 18 | assert.equal(whole, '0,000345,0,0,00789,-1234,5678,-09', '#1'); 19 | assert.equal(a[0].type, 'decimal', '#2'); 20 | assert.equal(a.foundIndex, 0, '#3'); 21 | assert.ok(a[0].match, '#4'); 22 | 23 | var r = incdec.getReplacement(a, 1); 24 | assert.equal(r.replacement, '1', '#5'); 25 | 26 | var r = incdec.getReplacement(a, 1000); 27 | assert.equal(r.replacement, '1000', '#6'); 28 | 29 | var r = incdec.getReplacement(a, 0x100000001); 30 | assert.equal(r.replacement, '1', '#7'); 31 | 32 | var r = incdec.getReplacement(a, -2); 33 | assert.equal(r.replacement, '-2', '#8'); 34 | }); 35 | 36 | it('should extract binary and decimal numbers', function () { 37 | var incdec = new Wasavi.IncDec; 38 | var a = incdec.extractTargets(target, 6, {formats: 'bin'}); 39 | var whole = a.map(item => item.text).join(','); 40 | assert.equal(whole, '0b000,345,0,0,00789,-1234,5678,-09', '#1'); 41 | assert.equal(a[0].type, 'bin', '#2'); 42 | assert.equal(a.foundIndex, 0, '#3'); 43 | assert.ok(a[0].match, '#4'); 44 | 45 | var r = incdec.getReplacement(a, 1); 46 | assert.equal(r.replacement, '0b001', '#5'); 47 | 48 | var r = incdec.getReplacement(a, 0b1000); 49 | assert.equal(r.replacement, '0b1000', '#6'); 50 | 51 | var r = incdec.getReplacement(a, 0x100000001); 52 | assert.equal(r.replacement, '0b001', '#7'); 53 | 54 | var r = incdec.getReplacement(a, -2); 55 | assert.equal(r.replacement, '0b11111111111111111111111111111110', '#8'); 56 | }); 57 | 58 | it('should extract hex and decimal numbers', function () { 59 | var incdec = new Wasavi.IncDec; 60 | var a = incdec.extractTargets(target, 16, {formats: 'hex'}); 61 | var whole = a.map(item => item.text).join(','); 62 | assert.equal(whole, '0,000345,0x0f,00789,-1234,5678,-09', '#1'); 63 | assert.equal(a[2].type, 'hex', '#2'); 64 | assert.equal(a.foundIndex, 2, '#3'); 65 | assert.ok(a[2].match, '#4'); 66 | 67 | var r = incdec.getReplacement(a, 1); 68 | assert.equal(r.replacement, '0x10', '#5'); 69 | 70 | var r = incdec.getReplacement(a, 0x1000); 71 | assert.equal(r.replacement, '0x100f', '#6'); 72 | 73 | var r = incdec.getReplacement(a, 0x100000001); 74 | assert.equal(r.replacement, '0x10', '#7'); 75 | 76 | var r = incdec.getReplacement(a, -17); 77 | assert.equal(r.replacement, '0xfffffffe', '#8'); 78 | }); 79 | 80 | it('should pick correct caps up for hex numbers', function () { 81 | var incdec = new Wasavi.IncDec; 82 | var a = incdec.extractTargets('0x0009', 0, {formats: 'hex'}); 83 | var r = incdec.getReplacement(a, 1); 84 | assert.equal(r.replacement, '0x000a', '#1'); 85 | 86 | var a = incdec.extractTargets('0X0009', 0, {formats: 'hex'}); 87 | var r = incdec.getReplacement(a, 1); 88 | assert.equal(r.replacement, '0X000A', '#2'); 89 | 90 | var a = incdec.extractTargets('0X000a', 0, {formats: 'hex'}); 91 | var r = incdec.getReplacement(a, 1); 92 | assert.equal(r.replacement, '0X000b', '#3'); 93 | 94 | var a = incdec.extractTargets('0x000A', 0, {formats: 'hex'}); 95 | var r = incdec.getReplacement(a, 1); 96 | assert.equal(r.replacement, '0x000B', '#4'); 97 | }); 98 | 99 | it('should extract octal and decimal numbers', function () { 100 | var incdec = new Wasavi.IncDec; 101 | var a = incdec.extractTargets(target, 10, {formats: 'octal'}); 102 | var whole = a.map(item => item.text).join(','); 103 | assert.equal(whole, '0,000345,0,0,00789,-1234,5678,-09', '#1'); 104 | assert.equal(a[1].type, 'octal', '#2'); 105 | assert.equal(a.foundIndex, 1, '#3'); 106 | assert.ok(a[1].match, '#4'); 107 | 108 | var r = incdec.getReplacement(a, 1); 109 | assert.equal(r.replacement, '000346', '#5'); 110 | 111 | var r = incdec.getReplacement(a, parseInt('01000', 8)); 112 | assert.equal(r.replacement, '001345', '#6'); 113 | 114 | var r = incdec.getReplacement(a, 0x100000001); 115 | assert.equal(r.replacement, '000346', '#7'); 116 | 117 | var r = incdec.getReplacement(a, -(2 + parseInt('000345', 8))); 118 | assert.equal(r.replacement, '037777777776', '#8'); 119 | }); 120 | 121 | it('should pick up dicimal number, not a octal', function () { 122 | var incdec = new Wasavi.IncDec; 123 | var a = incdec.extractTargets(target, 25, {formats: 'octal'}); 124 | assert.equal(a.foundIndex, 4, '#1'); 125 | assert.equal(a[4].text, '00789', '#2'); 126 | assert.equal(a[4].index, 23, '#3'); 127 | assert.equal(a[4].type, 'decimal', '#4'); 128 | 129 | var r = incdec.getReplacement(a, 1); 130 | assert.equal(r.replacement, '790', '#5'); 131 | 132 | var r = incdec.getReplacement(a, 1000); 133 | assert.equal(r.replacement, '1789', '#6'); 134 | 135 | var r = incdec.getReplacement(a, 0x100000001); 136 | assert.equal(r.replacement, '790', '#7'); 137 | 138 | var r = incdec.getReplacement(a, -(2 + 789)); 139 | assert.equal(r.replacement, '-2', '#8'); 140 | }); 141 | 142 | it('should extract alphabets and decimal numbers', function () { 143 | var incdec = new Wasavi.IncDec; 144 | var a = incdec.extractTargets(target, 4, {formats: 'alpha'}); 145 | var whole = a.map(item => item.text).join(','); 146 | assert.equal(whole, 'z,0,000345,0,0,00789,-1234,5678,-09', '#1'); 147 | assert.equal(a[0].type, 'alpha', '#2'); 148 | assert.equal(a.foundIndex, 0, '#3'); 149 | assert.ok(a[0].match, '#4'); 150 | 151 | var r = incdec.getReplacement(a, 1); 152 | assert.equal(r.replacement, 'a', '#5'); 153 | 154 | var r = incdec.getReplacement(a, -2); 155 | assert.equal(r.replacement, 'x', '#6'); 156 | }); 157 | 158 | it('should return quickly if firstReturn option is specified', function () { 159 | var incdec = new Wasavi.IncDec; 160 | var a = incdec.extractTargets(target, 4, {formats: 'alpha', firstReturn: true}); 161 | var whole = a.map(item => item.text).join(','); 162 | assert.equal(whole, 'z', '#1'); 163 | }); 164 | }); 165 | 166 | -------------------------------------------------------------------------------- /src/unit-tests/MapManager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | require('../chrome/frontend/qeema.js'); 8 | require('../chrome/frontend/classes.js'); 9 | 10 | describe('class MapManager', () => { 11 | function createMapManager () { 12 | const mm = new Wasavi.MapManager({ 13 | keyManager: qeema, 14 | config: { 15 | vars: { 16 | remap: true 17 | } 18 | } 19 | }); 20 | mm.maps.command.register('a', 'gg', true); 21 | mm.maps.command.register('b', 'B', true); 22 | mm.maps.command.register('bb', '^', true); 23 | mm.maps.command.register('h', 'l', true); 24 | mm.maps.command.register('Q', '1G', true); 25 | mm.maps.command.register('QQ', 'G', true); 26 | return mm; 27 | } 28 | 29 | it('should return an unmapped key as it is', done => { 30 | const mm = createMapManager(); 31 | 32 | // z -> z 33 | const e1 = qeema.parseKeyDesc('z').prop; 34 | mm.process('command', e1) 35 | .then(e => { 36 | assert.equal(e.code, e1.code); 37 | assert.equal(mm.isWaiting, false); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('should resolve unique mache', done => { 43 | const mm = createMapManager(); 44 | mm.onexpand = sequences => { 45 | assert.equal(sequences.length, 1); 46 | assert.equal(sequences[0].char, 'l'); 47 | assert.equal(mm.isWaiting, false); 48 | done(); 49 | }; 50 | 51 | // h -> l 52 | const e1 = qeema.parseKeyDesc('h').prop; 53 | mm.process('command', e1) 54 | .then(e => { 55 | assert.equal(e, undefined); 56 | }); 57 | }); 58 | 59 | it('should resolve ambiguous matches by timeout', done => { 60 | const mm = createMapManager(); 61 | mm.onexpand = sequences => { 62 | assert.equal(sequences.length, 1); 63 | assert.equal(sequences[0].char, 'B'); 64 | assert.equal(mm.isWaiting, false); 65 | done(); 66 | }; 67 | 68 | // b -> B 69 | const e1 = qeema.parseKeyDesc('b').prop; 70 | mm.process('command', e1) 71 | .then(e => { 72 | assert.equal(e, undefined); 73 | }); 74 | }); 75 | 76 | it('should resolve ambiguous matches by subsequent input', done => { 77 | const mm = createMapManager(); 78 | mm.onexpand = sequences => { 79 | assert.equal(sequences.length, 1); 80 | assert.equal(sequences[0].char, '^'); 81 | assert.equal(mm.isWaiting, false); 82 | done(); 83 | }; 84 | 85 | // bb -> ^ 86 | const e1 = qeema.parseKeyDesc('b').prop; 87 | mm.process('command', e1) 88 | .then(e => { 89 | assert.equal(e, undefined); 90 | return mm.process('command', e1); 91 | }) 92 | .then(e => { 93 | assert.equal(e, undefined); 94 | }); 95 | }); 96 | 97 | it('should resolve ambiguous matches by subsequent input in different mode', done => { 98 | const mm = createMapManager(); 99 | mm.onexpand = sequences => { 100 | assert.equal(sequences.length, 2); 101 | assert.equal(sequences[0].char, 'B'); 102 | assert.equal(sequences[0].overrideMap, 'command'); 103 | assert.equal(sequences[1].char, 'b'); 104 | assert.equal(sequences[1].overrideMap, 'edit'); 105 | assert.equal(mm.isWaiting, false); 106 | done(); 107 | }; 108 | 109 | // bb -> Bb 110 | const e1 = qeema.parseKeyDesc('b').prop; 111 | mm.process('command', e1) 112 | .then(e => { 113 | assert.equal(e, undefined); 114 | return mm.process('edit', e1); 115 | }) 116 | .then(e => { 117 | assert.equal(e, undefined); 118 | }); 119 | }); 120 | 121 | it('should resolve halfway input by timeout #1', done => { 122 | const mm = createMapManager(); 123 | mm.maps.command.remove('b', 'bb'); 124 | mm.maps.command.register('bbb', 'B'); 125 | mm.maps.command.register('bbbb', '^'); 126 | mm.onexpand = sequences => { 127 | assert.equal(sequences.length, 1); 128 | assert.equal(sequences[0].char, 'b'); 129 | assert.equal(sequences[0].isNoremap, true); 130 | assert.equal(mm.isWaiting, false); 131 | done(); 132 | }; 133 | 134 | // b -> b 135 | const e1 = qeema.parseKeyDesc('b').prop; 136 | mm.process('command', e1) 137 | .then(e => { 138 | assert.equal(e, undefined); 139 | }); 140 | }); 141 | 142 | it('should resolve halfway input by timeout #2', done => { 143 | const mm = createMapManager(); 144 | mm.maps.command.remove('b', 'bb'); 145 | mm.maps.command.register('bbb', 'B'); 146 | mm.maps.command.register('bbbb', '^'); 147 | mm.onexpand = sequences => { 148 | assert.equal(sequences.length, 2); 149 | assert.equal(sequences[0].char, 'b'); 150 | assert.equal(sequences[0].isNoremap, true); 151 | assert.equal(sequences[1].char, 'b'); 152 | assert.equal(sequences[1].isNoremap, true); 153 | assert.equal(mm.isWaiting, false); 154 | done(); 155 | }; 156 | 157 | // bb -> bb 158 | const e1 = qeema.parseKeyDesc('b').prop; 159 | mm.process('command', e1) 160 | .then(e => { 161 | assert.equal(e, undefined); 162 | return mm.process('command', e1); 163 | }) 164 | .then(e => { 165 | assert.equal(e, undefined); 166 | }); 167 | }); 168 | 169 | it('should resolve halfway input by unmapped key #1', done => { 170 | const mm = createMapManager(); 171 | mm.maps.command.remove('b', 'bb'); 172 | mm.maps.command.register('bbb', 'B'); 173 | mm.maps.command.register('bbbb', '^'); 174 | mm.onexpand = sequences => { 175 | assert.equal(sequences.length, 3, '#0'); 176 | assert.equal(sequences[0].char, 'b', '#1'); 177 | assert.equal(sequences[0].isNoremap, true, '#2'); 178 | assert.equal(sequences[1].char, 'b', '#3'); 179 | assert.equal(sequences[1].isNoremap, true, '#4'); 180 | assert.equal(sequences[2].char, 'Z', '#5'); 181 | assert.equal(sequences[2].isNoremap, true, '#6'); 182 | assert.equal(mm.isWaiting, false); 183 | done(); 184 | }; 185 | 186 | // bbZ -> bbZ 187 | mm.process('command', qeema.parseKeyDesc('b').prop) 188 | .then(e => { 189 | assert.equal(e, undefined); 190 | return mm.process('command', qeema.parseKeyDesc('b').prop); 191 | }) 192 | .then(e => { 193 | assert.equal(e, undefined); 194 | return mm.process('command', qeema.parseKeyDesc('Z').prop); 195 | }) 196 | .then(e => { 197 | assert.equal(e, undefined); 198 | }); 199 | }); 200 | 201 | it('should resolve halfway input by unmapped key #2', done => { 202 | const mm = createMapManager(); 203 | mm.onexpand = sequences => { 204 | assert.equal(sequences.length, 3); 205 | assert.equal(sequences[0].char, '1'); 206 | assert.equal(sequences[1].char, 'G'); 207 | assert.equal(sequences[2].char, 'j'); 208 | done(); 209 | }; 210 | 211 | // Qj -> 1Gj 212 | mm.process('command', qeema.parseKeyDesc('Q').prop) 213 | .then(e => { 214 | assert.equal(e, undefined); 215 | return mm.process('command', qeema.parseKeyDesc('j').prop); 216 | }) 217 | .then(e => { 218 | assert.equal(e, undefined); 219 | }); 220 | }); 221 | 222 | after(function (done) { 223 | this.timeout(1500); 224 | setTimeout(done, 1000); 225 | }); 226 | }); 227 | -------------------------------------------------------------------------------- /src/unit-tests/RegexConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | require('../chrome/frontend/classes.js'); 8 | 9 | describe('class RegexConverter', function () { 10 | var rc = new Wasavi.RegexConverter( 11 | // application proxy stub 12 | { 13 | config: { 14 | vars: { 15 | smartcase: false, 16 | ignorecase: false, 17 | wrapscan: false 18 | } 19 | } 20 | } 21 | ); 22 | 23 | var tests1 = [ 24 | ['foo', 'foo'], 25 | ['^f*o.o$', '^f*o.o$'], 26 | ['foo\\{1,2\\}bar\\?', 'foo{1,2}bar?'], 27 | ['\\(foo\\|bar\\)\\+', '(foo|bar)+'], 28 | ['a\\sb', 'a' + rc.SPECIAL_SPACE + 'b'], 29 | ['a\\Sb', 'a' + rc.SPECIAL_NONSPACE + 'b'], 30 | ['[abc\\s]', '[abc' + rc.SPECIAL_SPACE.replace(/^\[|\]$/g, '') + ']'], 31 | ['[abc\\S]', '[abc' + rc.SPECIAL_NONSPACE.replace(/^\[|\]$/g, '') + ']'], 32 | ['foo{1}', 'foo\\{1\\}'], 33 | ['foo(bar)', 'foo\\(bar\\)'], 34 | ['foo?bar+', 'foo\\?bar\\+'], 35 | ['foo\\d\\+', 'foo\\d+'], 36 | ['foo|bar', 'foo\\|bar'], 37 | ['(?foobar)', '\\(\\?foobar\\)'], 38 | ['\\(?foobar\\)', '(\\?foobar)'], 39 | ['\\t\\v\\\\', '\\t\\v\\\\'], 40 | ['\\t\\v\\', Error] 41 | ]; 42 | 43 | var tests2 = [ 44 | ['foo', 'foo'], 45 | ['^f*o.o$', '\\^f\\*o\\.o\\$'], 46 | ['foo\\{1,2\\}bar\\?', 'foo\\\\{1,2\\\\}bar\\\\?'], 47 | ['\\(foo\\|bar\\)\\+', '\\\\(foo\\\\|bar\\\\)\\\\+'], 48 | ['a\\sb', 'a\\\\sb'], 49 | ['a\\Sb', 'a\\\\Sb'], 50 | ['[abc\\s]', '\\[abc\\\\s\\]'], 51 | ['[abc\\S]', '\\[abc\\\\S\\]'], 52 | ['foo{1}', 'foo{1}'], 53 | ['foo(bar)', 'foo(bar)'], 54 | ['foo?bar+', 'foo?bar+'], 55 | ['foo\\d\\+', 'foo\\\\d\\\\+'], 56 | ['foo|bar', 'foo|bar'], 57 | ['(?foobar)', '(?foobar)'], 58 | ['\\(?foobar\\)', '\\\\(?foobar\\\\)'], 59 | ['\\t\\v\\\\', '\\\\t\\\\v\\\\\\\\'], 60 | ['\\t\\v\\', Error] 61 | ]; 62 | 63 | tests1.forEach((test, index) => { 64 | it(`should convert vi regex to js regex, #${index + 1}`, () => { 65 | if (test[1] == Error) { 66 | assert.throws(() => rc.toJsRegexString(test[0])); 67 | } 68 | else { 69 | var result = rc.toJsRegexString(test[0]); 70 | assert.equal(result, test[1]); 71 | 72 | var result = rc.toJsRegex(test[0]); 73 | assert.ok(result instanceof RegExp); 74 | } 75 | }); 76 | }); 77 | 78 | tests2.forEach((test, index) => { 79 | it(`should convert vi regex to js literal string, #${index + 1}`, () => { 80 | if (test[1] == Error) { 81 | assert.throws(() => rc.toLiteralString(test[0])); 82 | } 83 | else { 84 | var result = rc.toLiteralString(test[0]); 85 | assert.equal(result, test[1]); 86 | } 87 | }); 88 | }); 89 | }); 90 | 91 | -------------------------------------------------------------------------------- /src/unit-tests/SortWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | require('../chrome/frontend/classes.js'); 8 | 9 | const unicodeUtils = require('../chrome/frontend/unicode_utils.js').unicodeUtils; 10 | global.spc = unicodeUtils.getUnicodeGeneralSpaceRegex; 11 | 12 | describe('class SortWorker', function () { 13 | var sw = new Wasavi.SortWorker( 14 | // application proxy stub 15 | { 16 | low: { 17 | getFindRegex: function (src) { 18 | return new RegExp(src.pattern); 19 | } 20 | } 21 | }, 22 | // buffer stub 23 | { 24 | }, 25 | // arguments stub 26 | { 27 | flags: {} 28 | } 29 | ); 30 | var tests = [ 31 | ['', 'xyz\nfoo\nbar', 'bar\nfoo\nxyz\n'], 32 | ['', 'xyz \\\nfoo \\\nbar', 'bar \\\nfoo \\\nxyz\n'], 33 | ['', 'xyz,\nfoo,\nbar', 'bar,\nfoo,\nxyz\n'], 34 | ['i', 'Foo\nbar\nBAZ', 'bar\nBAZ\nFoo\n'], 35 | ['/\\d+/', 'foo4baz\nbar3bar\nbaz1frr', 'bar3bar\nfoo4baz\nbaz1frr\n'], 36 | ['/\\d+/r', 'foo4baz\nbar3bar\nbaz1frr', 'baz1frr\nbar3bar\nfoo4baz\n'], 37 | ['/\\d+/i', 'foo4baZ\nbar3bar\nbaz1frr', 'bar3bar\nfoo4baZ\nbaz1frr\n'], 38 | ['/[a-zA-Z]+/ri', '345c847\n123a456\n537B183', '123a456\n537B183\n345c847\n'], 39 | ['c3', 'foo897\nbar532\nzoo321', 'zoo321\nbar532\nfoo897\n'], 40 | ['c3i', '321zoo\n532bar\n897Foo', '532bar\n897Foo\n321zoo\n'] 41 | ]; 42 | 43 | tests.forEach(function (test, index) { 44 | it(`should sort, test #${index + 1}`, function () { 45 | /* 46 | console.log([ 47 | '********', 48 | ` args: "${test[0]}"`, 49 | ` content: "${test[1]}"`, 50 | `expected result: "${test[2]}"` 51 | ].join('\n')); 52 | */ 53 | 54 | var result = sw.parseArgs(test[0]); 55 | assert.equal(isString(result), false, 'test parse result is not a string'); 56 | 57 | sw.buildContent(test[1]); 58 | 59 | var result = sw.sort(); 60 | assert.equal(isString(result), false, 'test sort result is not a string'); 61 | 62 | var result = sw.getContent(); 63 | assert.equal(result, test[2], 'test sort result is correct'); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/unit-tests/expr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | 8 | describe('expr', () => { 9 | it('should return empty set with empty string', () => { 10 | var e = expr(''); 11 | assert.equal(typeof e, 'object'); 12 | assert.equal(Object.keys(e).length, 0); 13 | }); 14 | 15 | it('should throw error with invalid expression', () => { 16 | var e = expr('foobar'); 17 | assert.equal(typeof e, 'object'); 18 | assert.ok('error' in e); 19 | assert.ok(/^Invalid token:/.test(e.error), e.error); 20 | }); 21 | 22 | it('should throw error with an expression which has extra token', () => { 23 | var e = expr('(1)2'); 24 | assert.equal(typeof e, 'object'); 25 | assert.ok('error' in e); 26 | assert.ok(/^Extra token:/.test(e.error), e.error); 27 | }); 28 | 29 | it('should throw error with unbalance paren', () => { 30 | var e = expr('(1'); 31 | assert.equal(typeof e, 'object'); 32 | assert.ok('error' in e); 33 | assert.ok(/^Missing "\)"\./.test(e.error), e.error); 34 | }); 35 | 36 | it('should throw error with incomplete plus sign', () => { 37 | var e = expr('+'); 38 | assert.equal(typeof e, 'object'); 39 | assert.ok('error' in e); 40 | assert.ok(/^Missing a number\./.test(e.error), e.error); 41 | }); 42 | 43 | it('should throw error with incomplete exponential float', () => { 44 | var e = expr('1.0e'); 45 | assert.equal(typeof e, 'object'); 46 | assert.ok('error' in e); 47 | assert.ok(/^Invalid token:/.test(e.error), e.error); 48 | }); 49 | 50 | it('should throw error with incomplete hex expression', () => { 51 | var e = expr('0x'); 52 | assert.equal(typeof e, 'object'); 53 | assert.ok('error' in e); 54 | assert.ok(/^Invalid token:/.test(e.error), e.error); 55 | }); 56 | 57 | it('should recognize + as plus sign', () => { 58 | var e = expr('+1'); 59 | assert.equal(typeof e, 'object'); 60 | assert.ok('result' in e); 61 | assert.equal(e.result, 1); 62 | }); 63 | 64 | it('should recognize + as plus sign, even if separated', () => { 65 | var e = expr('+ 1'); 66 | assert.equal(typeof e, 'object'); 67 | assert.ok('result' in e); 68 | assert.equal(e.result, 1); 69 | }); 70 | 71 | it('should parse an integer', () => { 72 | var e = expr('1234'); 73 | assert.equal(typeof e, 'object'); 74 | assert.ok('result' in e); 75 | assert.equal(1234, e.result); 76 | }); 77 | 78 | it('should parse a float', () => { 79 | var e = expr('0.001'); 80 | assert.equal(typeof e, 'object'); 81 | assert.ok('result' in e); 82 | assert.equal(0.001, e.result); 83 | }); 84 | 85 | it('should parse a natural number omitted float', () => { 86 | var e = expr('.002'); 87 | assert.equal(typeof e, 'object'); 88 | assert.ok('result' in e); 89 | assert.equal(0.002, e.result); 90 | }); 91 | 92 | it('should parse a decimal omitted float', () => { 93 | var e = expr('2.'); 94 | assert.equal(typeof e, 'object'); 95 | assert.ok('result' in e); 96 | assert.equal(2, e.result); 97 | }); 98 | 99 | [ 100 | '1e1', '1e+1', '1e-1', 101 | '1.1e1', '1.1e+1', '1.1e-1', 102 | '.1e1', '.1e+1', '.1e-1' 103 | ].forEach((n) => { 104 | it(`should parse an exponential float (${n})`, () => { 105 | var e = expr(n); 106 | assert.equal(typeof e, 'object'); 107 | assert.ok('result' in e); 108 | assert.equal(parseFloat(n), e.result); 109 | }); 110 | }); 111 | 112 | it('should parse hex number', () => { 113 | var e = expr('0x1f'); 114 | assert.equal(typeof e, 'object'); 115 | assert.ok('result' in e); 116 | assert.equal(0x1f, e.result); 117 | }); 118 | 119 | it('should parse octal number', () => { 120 | var e = expr('0777'); 121 | assert.equal(typeof e, 'object'); 122 | assert.ok('result' in e); 123 | assert.equal(511, e.result); 124 | }); 125 | 126 | it('should parse binaly number', () => { 127 | var e = expr('0b1111_1111'); 128 | assert.equal(typeof e, 'object'); 129 | assert.ok('result' in e); 130 | assert.equal(255, e.result); 131 | }); 132 | 133 | it('should compute multiply expression', () => { 134 | var e = expr('2 * 3'); 135 | assert.equal(typeof e, 'object'); 136 | assert.ok('result' in e); 137 | assert.equal(6, e.result); 138 | }); 139 | 140 | it('should compute divide expression', () => { 141 | var e = expr('2 / 3'); 142 | assert.equal(typeof e, 'object'); 143 | assert.ok('result' in e); 144 | assert.ok(/0.6+/.test(e.result), e.result); 145 | }); 146 | 147 | it('should compute modulo expression', () => { 148 | var e = expr('5 % 2'); 149 | assert.equal(typeof e, 'object'); 150 | assert.ok('result' in e); 151 | assert.equal(1, e.result); 152 | }); 153 | 154 | it('should compute add expression', () => { 155 | var e = expr('1 + -1'); 156 | assert.equal(typeof e, 'object'); 157 | assert.ok('result' in e); 158 | assert.equal(0, e.result); 159 | }); 160 | 161 | it('should compute subtract expression', () => { 162 | var e = expr('1 - -1'); 163 | assert.equal(typeof e, 'object'); 164 | assert.ok('result' in e); 165 | assert.equal(2, e.result); 166 | }); 167 | 168 | it('should compute complex expression', () => { 169 | var e = expr('2 + 3 * 6'); 170 | assert.equal(typeof e, 'object'); 171 | assert.ok('result' in e, JSON.stringify(e)); 172 | assert.equal(20, e.result); 173 | }); 174 | 175 | it('should compute expression containing parenthesis', () => { 176 | var e = expr('(2 + 3) * 6'); 177 | assert.equal(typeof e, 'object'); 178 | assert.ok('result' in e); 179 | assert.equal(30, e.result); 180 | }); 181 | }); 182 | -------------------------------------------------------------------------------- /src/unit-tests/splitex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | 8 | describe('splitex', () => { 9 | it('should be split into exactly N elements', () => { 10 | let a = splitex('abc def ghi jkl mno pqr stu vwx yz', ' ', 3); 11 | assert.equal(a.length, 3); 12 | assert.equal(a[0], 'abc'); 13 | assert.equal(a[1], 'def'); 14 | assert.equal(a[2], 'ghi jkl mno pqr stu vwx yz'); 15 | }); 16 | 17 | it('should be split into exactly N elements even if number of elements less than N', () => { 18 | let a = splitex('abc def', ' ', 3); 19 | assert.equal(a.length, 3); 20 | assert.equal(a[0], 'abc'); 21 | assert.equal(a[1], 'def'); 22 | assert.equal(a[2], ''); 23 | }); 24 | 25 | it('should return empty array if N is zero', () => { 26 | let a = splitex('abc def ghi', ' ', 0); 27 | assert.equal(a.length, 0); 28 | }); 29 | 30 | it('should be split into exactly N elements (RegExp)', () => { 31 | let a = splitex('abc def ghi jkl mno pqr stu vwx\tyz', /\s+/, 3); 32 | assert.equal(a.length, 3); 33 | assert.equal(a[0], 'abc'); 34 | assert.equal(a[1], 'def'); 35 | assert.equal(a[2], 'ghi jkl mno pqr stu vwx\tyz'); 36 | }); 37 | 38 | it('should be split into exactly N elements even if number of elements less than N (RegExp)', () => { 39 | let a = splitex('abc def', /\s+/, 3); 40 | assert.equal(a.length, 3); 41 | assert.equal(a[0], 'abc'); 42 | assert.equal(a[1], 'def'); 43 | assert.equal(a[2], ''); 44 | 45 | a = splitex('abc def ghi ', /\s+/, 3); 46 | assert.equal(a.length, 3); 47 | assert.equal(a[0], 'abc'); 48 | assert.equal(a[1], 'def'); 49 | assert.equal(a[2], 'ghi'); 50 | }); 51 | 52 | it('should return empty array if N is zero (RegExp)', () => { 53 | let a = splitex('abc def ghi', /\s+/, 0); 54 | assert.equal(a.length, 0); 55 | }); 56 | 57 | it('should throw an error if num is not a number', () => { 58 | assert.throws( 59 | () => splitex('abc def', /\s+/, 'abc'), 60 | Error, 'splited: num is not a number'); 61 | 62 | assert.throws( 63 | () => splitex('abc def', /\s+/, NaN), 64 | Error, 'splited: num is not a number'); 65 | }); 66 | 67 | it('should throw an error if delemiter is invalid', () => { 68 | assert.throws( 69 | () => splitex('abc def', 100, 100), 70 | Error, 'splitex: delimiter is neither string nor RegExp'); 71 | 72 | assert.throws( 73 | () => splitex('abc def', {}, 100), 74 | Error, 'splitex: delimiter is neither string nor RegExp'); 75 | }); 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /src/unit-tests/strftime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const exec = require('child_process').exec; 5 | 6 | require('../chrome/frontend/init.js'); 7 | require('../chrome/frontend/utils.js'); 8 | 9 | const formats = [ 10 | 'a: %a', 11 | 'A: %A', 12 | 'b: %b', 13 | 'B: %B', 14 | 'c: %c', 15 | 'C: %C ~%4C~ ~%04C~ ~%_4C~ ~%-C~', 16 | 'd: %d ~%4d~ ~%04d~ ~%_4d~ ~%-d~', 17 | 'D: %D', 18 | 'e: %e ~%4e~ ~%04e~ ~%_4e~ ~%-e~', 19 | 'F: %F', 20 | 'g: %g ~%4g~ ~%04g~ ~%_4g~ ~%-g~', 21 | 'G: %G ~%4G~ ~%04G~ ~%_4G~ ~%-G~', 22 | 'h: %h', 23 | 'H: %H ~%4H~ ~%04H~ ~%_4H~ ~%-H~', 24 | 'I: %I ~%4I~ ~%04I~ ~%_4I~ ~%-I~', 25 | 'j: %j ~%4j~ ~%04j~ ~%_4j~ ~%-j~', 26 | 'k: %k ~%4k~ ~%04k~ ~%_4k~ ~%-k~', 27 | 'l: %l ~%4l~ ~%04l~ ~%_4l~ ~%-l~', 28 | 'm: %m ~%4m~ ~%04m~ ~%_4m~ ~%-m~', 29 | 'M: %M ~%4M~ ~%04M~ ~%_4M~ ~%-M~', 30 | //'n: %n', 31 | 'p: %p ~%^p~ ~%#p~', 32 | 'P: %P ~%^P~ ~%#P~', 33 | 'r: %r', 34 | 'R: %R', 35 | 's: %s ~%4s~ ~%04s~ ~%_4s~ ~%-s~', 36 | 'S: %S ~%4S~ ~%04S~ ~%_4S~ ~%-S~', 37 | 't: %t', 38 | 'T: %T', 39 | 'u: %u ~%4u~ ~%04u~ ~%_4u~ ~%-u~', 40 | 'U: %U ~%4U~ ~%04U~ ~%_4U~ ~%-U~', 41 | 'V: %V ~%4V~ ~%04V~ ~%_4V~ ~%-V~', 42 | 'w: %w ~%4w~ ~%04w~ ~%_4w~ ~%-w~', 43 | 'W: %W ~%4W~ ~%04W~ ~%_4W~ ~%-W~', 44 | 'x: %x', 45 | 'X: %X', 46 | 'y: %y ~%4y~ ~%04y~ ~%_4y~ ~%-y~', 47 | 'Y: %Y ~%4Y~ ~%04Y~ ~%_4Y~ ~%-Y~', 48 | 'z: %z ~%^z~ ~%#z~', 49 | 'Z: %Z ~%^Z~ ~%#Z~' 50 | ]; 51 | const format = formats.join('###'); 52 | const testDate = '2017-01-02 03:04:05'; 53 | 54 | describe('strftime', () => { 55 | var child; 56 | var expected; 57 | var actual; 58 | 59 | before(done => { 60 | child = exec( 61 | `LANG=en date --date="${testDate}" +"${format}"`, 62 | (error, stdout, stderr) => { 63 | if (error) { 64 | done(error); 65 | } 66 | expected = stdout.replace(/\n$/, '').split('###'); 67 | actual = strftime('%{locale:en}' + format, new Date(testDate)); 68 | done(); 69 | } 70 | ); 71 | }); 72 | 73 | it('should be a string', () => { 74 | assert.equal(typeof actual, 'string'); 75 | actual = actual.split('###'); 76 | }); 77 | 78 | it('should be same number of elements as expected', () => { 79 | assert.equal(actual.length, expected.length); 80 | }); 81 | 82 | formats.forEach((f, i) => { 83 | it(`format "${f}"`, () => { 84 | assert.equal(actual[i], expected[i]); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/unit-tests/toVisibleString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | require('../chrome/frontend/init.js'); 6 | require('../chrome/frontend/utils.js'); 7 | 8 | describe('function toVisibleString', function () { 9 | it('should return printable chars as it is', () => { 10 | assert.equal(toVisibleString('foo'), 'foo'); 11 | }); 12 | 13 | it('should return printable falsy chars as it is', () => { 14 | assert.equal(toVisibleString('0'), '0'); 15 | assert.equal(toVisibleString('false'), 'false'); 16 | assert.equal(toVisibleString('null'), 'null'); 17 | assert.equal(toVisibleString('undefined'), 'undefined'); 18 | assert.equal(toVisibleString('NaN'), 'NaN'); 19 | }); 20 | 21 | it('should return 0 as "0"', () => { 22 | assert.equal(toVisibleString(0), '0', '0'); 23 | }); 24 | 25 | it('should return falsy values as empty string', () => { 26 | assert.equal(toVisibleString(false), '', 'false'); 27 | assert.equal(toVisibleString(null), '', 'null'); 28 | assert.equal(toVisibleString(undefined), '', 'undefined'); 29 | assert.equal(toVisibleString(NaN), '', 'NaN'); 30 | }); 31 | 32 | it('should return control chars as special form', () => { 33 | assert.equal(toVisibleString('\u001aabc'), '^Zabc', '^Z'); 34 | assert.equal(toVisibleString('\u007fabc'), '^_abc', '^_'); 35 | }); 36 | 37 | it('should return an object as empty string', () => { 38 | assert.equal(toVisibleString({foo: 'bar'}), '', 'object'); 39 | }); 40 | 41 | it('should return an array as special string', () => { 42 | assert.equal(toVisibleString([0,1,2]), '0, 1, 2', 'array'); 43 | assert.equal( 44 | toVisibleString([0,1,2,3,4,5,6,7,8,9,10]), 45 | '0, 1, 2, 3, 4, 5, 6, 7, 8, 9...', 46 | 'array'); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/unit-tests/unicode_linebreaker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const fs = require('fs'); 5 | 6 | require('../chrome/frontend/init.js'); 7 | require('../chrome/frontend/utils.js'); 8 | 9 | const unicodeUtils = require('../chrome/frontend/unicode_utils.js').unicodeUtils; 10 | 11 | function loadDict () { 12 | return fs.readFileSync( 13 | __dirname + '/../chrome/unicode/linebreak.dat', 14 | 'binary'); 15 | } 16 | 17 | function loadTestData () { 18 | return fs.readFileSync( 19 | __dirname + '/../unicode-tools/ucd/auxiliary/LineBreakTest.txt', 20 | 'utf8'); 21 | } 22 | 23 | describe('class LineBreakder', function () { 24 | var dictData = loadDict(); 25 | var testData = loadTestData(); 26 | var count = 0; 27 | var countLimit = -1; 28 | var lineBreaker = new unicodeUtils.LineBreaker(dictData); 29 | 30 | testData.split('\n').forEach(function (line) { 31 | // cut a comment off 32 | var comment = /#(.*)$/.exec(line); 33 | line = line.replace(/#.*$/, '').replace(/^\s+|\s+$/g, ''); 34 | 35 | // return if the line is just a comment 36 | if (line == '') return; 37 | 38 | count++; 39 | 40 | // for debug 41 | if (countLimit > 0 && count > countLimit) return; 42 | 43 | // parse the rule. 44 | // example: 45 | // × 0023 × 0023 ÷ 46 | // × 0023 × 0020 ÷ 0023 ÷ 47 | var s = ''; 48 | var codePointCount = 0; 49 | var rules = []; 50 | line.split(/\s+/).forEach(function (node) { 51 | if (/^[0-9a-fA-F]+$/.test(node)) { 52 | s += unicodeUtils.toUTF16(parseInt(node, 16)); 53 | codePointCount++; 54 | } 55 | else { 56 | switch (node.charCodeAt(0)) { 57 | case 0x00d7:// × prohibited 58 | rules.push(false); 59 | break; 60 | 61 | case 0x00f7:// ÷ allowed 62 | rules.push(true); 63 | break; 64 | } 65 | } 66 | }); 67 | 68 | // make test function 69 | it(`test #${count} (${line})`, (function (count, codePointCount, s, rules) { 70 | return function () { 71 | assert.equal(codePointCount, rules.length - 1); 72 | 73 | var lb = lineBreaker.run(s); 74 | assert.ok(lb, 'ensure line break info is an object'); 75 | assert.ok('length' in lb, 'ensure line info has length property'); 76 | 77 | /* 78 | console.log( 79 | 'test #' + count + ', line: "' + line + '", "' + s + '"\n' + 80 | JSON.stringify(lb, null, ' ') 81 | ); 82 | */ 83 | 84 | assert.equal(lb.length, rules.length - 1, 'test line break info count'); 85 | 86 | for (var i = 0, goal = lb.length; i < goal; i++) { 87 | assert.strictEqual( 88 | unicodeUtils.canBreak(lb[i].breakAction), 89 | rules[i + 1], 90 | 'index ' + i); 91 | } 92 | } 93 | })(count, codePointCount, s, rules)); 94 | }); 95 | }); 96 | 97 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker : 98 | -------------------------------------------------------------------------------- /src/wd-tests/almost-min.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const events = require('events'); 4 | const mocha = require('mocha'); 5 | const Base = require('mocha/lib/reporters/base'); 6 | const inherits = require('mocha/lib/utils').inherits; 7 | 8 | var doneStats = []; 9 | var currentStats; 10 | var numberOfAllTests = -1; 11 | 12 | function calcNumber (suite) { 13 | var result = suite.tests.length; 14 | 15 | for (var i = 0, goal = suite.suites.length; i < goal; i++) { 16 | result += calcNumber(suite.suites[i]); 17 | } 18 | 19 | return result; 20 | } 21 | 22 | function printResult () { 23 | var longestTitle = ''; 24 | var maxPasses = 0; 25 | var maxFailures = 0; 26 | var maxTests = 0; 27 | 28 | doneStats.forEach(s => { 29 | if (s.tests && s.passes == s.tests) { 30 | return; 31 | } 32 | 33 | if (s.title.length > longestTitle.length) { 34 | longestTitle = s.title; 35 | } 36 | if (s.passes > maxPasses) { 37 | maxPasses = s.passes; 38 | } 39 | if (s.failures > maxFailures) { 40 | maxFailures = s.failures; 41 | } 42 | if (s.tests > maxTests) { 43 | maxTests = s.tests; 44 | } 45 | }); 46 | 47 | longestTitle = longestTitle.replace(/./g, ' '); 48 | maxPasses = ('' + maxPasses).replace(/./g, ' '); 49 | maxFailures = ('' + maxFailures).replace(/./g, ' '); 50 | maxTests = ('' + maxTests).replace(/./g, ' '); 51 | 52 | doneStats.forEach(s => { 53 | if (s.tests && s.passes == s.tests) { 54 | return; 55 | } 56 | 57 | var title = (s.title + longestTitle).substring(0, longestTitle.length); 58 | if (s.tests) { 59 | // "100.00" 60 | var ratio = (' ' + (s.passes / s.tests * 100).toFixed(2)).substr(-6); 61 | 62 | console.log( 63 | title + ' -- failed ' + 64 | (maxPasses + s.failures).substr(-maxFailures.length) + ' of ' + 65 | (maxTests + s.tests).substr(-maxTests.length) + 66 | ' (coverage: ' + ratio + '%)'); 67 | } 68 | else { 69 | console.log(title + ' -- N/A'); 70 | } 71 | }); 72 | } 73 | 74 | function AlmostMin (runner) { 75 | Base.call(this, runner); 76 | 77 | runner.on('start', _ => { 78 | AlmostMin.events.emit('start'); 79 | }); 80 | 81 | runner.on('suite', (suite) => { 82 | try { 83 | if (suite.root) { 84 | numberOfAllTests = calcNumber(suite); 85 | } 86 | 87 | if (suite.title == '') return; 88 | 89 | if (currentStats) { 90 | // merge the number of tests into top level suite 91 | currentStats.tests += suite.tests.length; 92 | } 93 | else { 94 | currentStats = { 95 | title: suite.title, 96 | tests: suite.tests.length, 97 | passes: 0, 98 | failures: 0 99 | }; 100 | } 101 | } 102 | catch (ex) { 103 | console.error(ex.stack); 104 | } 105 | 106 | AlmostMin.events.emit('suite', suite, numberOfAllTests); 107 | }); 108 | 109 | runner.on('hook', (hook) => { 110 | AlmostMin.events.emit('hook', hook); 111 | }); 112 | 113 | runner.on('hook end', (hook) => { 114 | AlmostMin.events.emit('hook end', hook); 115 | }); 116 | 117 | runner.on('test', (test) => { 118 | AlmostMin.events.emit('test', test); 119 | }); 120 | 121 | runner.on('pass', (test) => { 122 | currentStats && currentStats.passes++; 123 | 124 | AlmostMin.events.emit('pass', test); 125 | }); 126 | 127 | runner.on('fail', (test) => { 128 | currentStats && currentStats.failures++; 129 | 130 | AlmostMin.events.emit('fail', test); 131 | }); 132 | 133 | runner.on('pending', (test) => { 134 | AlmostMin.events.emit('pending', test); 135 | }); 136 | 137 | runner.on('test end', (test) => { 138 | AlmostMin.events.emit('test end', test); 139 | }); 140 | 141 | runner.on('suite end', (suite) => { 142 | try { 143 | if (suite.title == '') return; 144 | 145 | if (currentStats) { 146 | doneStats.push(currentStats); 147 | currentStats = undefined; 148 | } 149 | } 150 | catch (ex) { 151 | console.error(ex.stack); 152 | } 153 | 154 | AlmostMin.events.emit('suite end', suite); 155 | }); 156 | 157 | runner.on('end', _ => { 158 | try { 159 | printResult(); 160 | this.epilogue(); 161 | } 162 | catch (ex) { 163 | console.error(ex.stack); 164 | } 165 | 166 | AlmostMin.events.emit('end'); 167 | }); 168 | } 169 | 170 | inherits(AlmostMin, Base); 171 | 172 | AlmostMin.events = new events.EventEmitter; 173 | 174 | exports = module.exports = AlmostMin; 175 | -------------------------------------------------------------------------------- /src/wd-tests/app-mode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {By, Key, until} = require('selenium-webdriver'); 4 | const {it} = require('selenium-webdriver/testing'); 5 | 6 | exports.suite = (assert, wasavi, driver) => { 7 | it('file', function* () { 8 | yield wasavi.send('i!\u001b:file\n'); 9 | assert.eq('#1-1', '*Untitled* [unix, modified] line 1 of 1 (0%)', yield wasavi.getAppModeStatusLine()); 10 | }); 11 | 12 | it('file for rename', function* () { 13 | yield wasavi.send(':cd foo/bar|file ../baz/test.txt\n'); 14 | assert.eq('#1-1', 'dropbox:/foo/baz/test.txt [unix] --No lines in buffer--', yield wasavi.getAppModeStatusLine()); 15 | yield wasavi.send('\u001b'); 16 | assert.eq('#1-2', '/foo/baz/test.txt', yield wasavi.getAppModeStatusLine()); 17 | yield wasavi.send(':cd /\n'); 18 | assert.eq('#1-3', 'foo/baz/test.txt', yield wasavi.getAppModeStatusLine()); 19 | 20 | yield wasavi.send(':cd foo|file\n'); 21 | assert.eq('#2-1', 'dropbox:/foo/baz/test.txt [unix] --No lines in buffer--', yield wasavi.getAppModeStatusLine()); 22 | yield wasavi.send('\u001b'); 23 | assert.eq('#2-2', 'baz/test.txt', yield wasavi.getAppModeStatusLine()); 24 | }); 25 | 26 | it('write new file', function* () { 27 | yield wasavi.send(':write\n'); 28 | assert.eq('#1-1', 'write: No file name.', yield wasavi.getAppModeStatusLine()); 29 | }); 30 | 31 | it('read 404', function* () { 32 | yield wasavi.send(':r noexist.txt\n'); 33 | assert.eq('#1-1', 'read: Cannot open "/noexist.txt".', yield wasavi.getAppModeStatusLine()); 34 | }); 35 | 36 | it('chdir', function* () { 37 | yield wasavi.send(':chdir wasavi-test|file\n'); 38 | yield wasavi.send(':pwd\n'); 39 | assert.eq('#1-1', 'dropbox:/wasavi-test', yield wasavi.getAppModeStatusLine()); 40 | }); 41 | 42 | it('chdir 404', function* () { 43 | yield wasavi.send(':chdir noexist|file\n'); 44 | assert.eq('#1-1', 'chdir: Invalid path.', yield wasavi.getAppModeStatusLine()); 45 | }); 46 | 47 | it('chdir noarg', function* () { 48 | yield wasavi.send(':chdir\n'); 49 | assert.eq('#1-1', 'dropbox:/', yield wasavi.getAppModeStatusLine()); 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /src/wd-tests/filesystem-test-files/hello-wasavi.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/wd-tests/filesystem-test-files/hello-wasavi.txt -------------------------------------------------------------------------------- /src/wd-tests/filesystem-test-files/wasavi-test/read test.txt: -------------------------------------------------------------------------------- 1 | hello,\nworld -------------------------------------------------------------------------------- /src/wd-tests/filesystem-test-files/wasavi-test/write test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/src/wd-tests/filesystem-test-files/wasavi-test/write test.txt -------------------------------------------------------------------------------- /src/wd-tests/filesystem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {By, Key, until, promise} = require('selenium-webdriver'); 4 | const {it, describe} = require('selenium-webdriver/testing'); 5 | 6 | exports.suite = (assert, wasavi, driver) => { 7 | const {ctrln, ctrlt, ctrlw} = assert.shortcuts; 8 | 9 | function* completeRootPath () { 10 | var args = Array.prototype.slice.call(arguments); 11 | 12 | for (var i = 0; i < args.length; i += 4) { 13 | var testLabel = args[i + 0]; 14 | var fs = args[i + 1]; 15 | var prefix = args[i + 2]; 16 | var makeDefault = args[i + 3]; 17 | 18 | if (makeDefault) { 19 | yield wasavi.send(':files default ' + fs + '\n'); 20 | } 21 | 22 | yield wasavi.setInputModeOfWatchTarget('line_input'); 23 | 24 | if (makeDefault) { 25 | yield wasavi.send(':r ' + prefix + '\t'); 26 | } 27 | else { 28 | yield wasavi.send(':r ' + fs + ':' + prefix + '\t'); 29 | } 30 | 31 | var line = wasavi.getLineInput(); 32 | yield wasavi.send('\u001b'); 33 | 34 | /* 35 | console.log( 36 | `completeRootPath: label:${testLabel} fs:${fs} prefix:${prefix} makeDefault:${makeDefault} line:${line}`); 37 | */ 38 | 39 | if (makeDefault) { 40 | assert.t(testLabel, (new RegExp(`^r ${prefix}.+$`)).test(line)); 41 | } 42 | else { 43 | assert.t(testLabel, (new RegExp(`^r ${fs}:${prefix}.+$`)).test(line)); 44 | } 45 | } 46 | } 47 | 48 | function* completeSubPath () { 49 | var args = Array.prototype.slice.call(arguments); 50 | 51 | for (var i = 0; i < args.length; i += 3) { 52 | var testLabel = args[i + 0]; 53 | var fs = args[i + 1]; 54 | var makeDefault = args[i + 2]; 55 | 56 | if (makeDefault) { 57 | yield wasavi.send(':files default ' + fs + '\n'); 58 | } 59 | 60 | wasavi.setInputModeOfWatchTarget('line_input'); 61 | 62 | if (makeDefault) { 63 | yield wasavi.send(':r /wasavi-test\t'); 64 | } 65 | else { 66 | yield wasavi.send(':r ' + fs + ':' + '/wasavi-test\t'); 67 | } 68 | 69 | var line = wasavi.getLineInput(); 70 | yield wasavi.send('\u001b'); 71 | 72 | yield wasavi.log( 73 | `completeRootPath: label:${testLabel} fs:${fs} makeDefault:${makeDefault} line:${line}`); 74 | 75 | if (makeDefault) { 76 | assert.t(testLabel, /^r \/wasavi-test.+$/.test(line)); 77 | } 78 | else { 79 | assert.t(testLabel, (new RegExp(`^r ${fs}:/wasavi-test.+$`)).test(line)); 80 | } 81 | } 82 | } 83 | 84 | function* read (fs) { 85 | yield wasavi.send(':0r ' + fs + ':/wasavi-test/read\\ test.txt\n'); 86 | assert.eq('#1-1', 'hello,\nworld', wasavi.getValue()); 87 | } 88 | 89 | function* write (fs) { 90 | var n = Math.floor(Math.random() * 1000); 91 | yield wasavi.send(':files default ' + fs + '\n'); 92 | yield wasavi.send(`ggawrite test:${n}\nwrite test\u001b`); 93 | wasavi.setInputModeOfWatchTarget('write handler'); 94 | yield wasavi.send(':w /wasavi-test/write\\ test.txt\n'); 95 | yield wasavi.send(':r /wasavi-test/write\\ test.txt\n'); 96 | assert.eq( 97 | '#1-1', 98 | `write test:${n}\nwrite test\nwrite test:${n}\nwrite test`, 99 | wasavi.getValue()); 100 | } 101 | 102 | it('file system command_default', function* () { 103 | this.timeout(1000 * 60); 104 | yield wasavi.send(':files default\n'); 105 | assert.t('#1-1', /^default file system: .+$/.test(wasavi.getLastMessage())); 106 | }); 107 | 108 | // dropbox 109 | 110 | it('complete root path dropbox', function* () { 111 | this.timeout(1000 * 60); 112 | promise.consume(completeRootPath, null, 113 | // drive name ommited, no-prefix 114 | '#1-1', 'dropbox', '', true, 115 | // drive name ommited, relative path prefixed 116 | '#1-2', 'dropbox', 'hello', true, 117 | // drive name ommited, absolute path prefixed 118 | '#1-3', 'dropbox', '/hello', true, 119 | 120 | // drive name specified, no-prefix 121 | '#2-1', 'dropbox', '', false, 122 | // drive name specified, relative path prefixed 123 | '#2-2', 'dropbox', 'hello', false, 124 | // drive name specified, absolute path prefixed 125 | '#2-3', 'dropbox', '/hello', false 126 | ); 127 | }); 128 | 129 | it('complete sub path dropbox', function* () { 130 | this.timeout(1000 * 60); 131 | promise.consume(completeSubPath, null, 132 | '#1', 'dropbox', true, 133 | '#2', 'dropbox', false 134 | ); 135 | }); 136 | 137 | it('read dropbox', function* () { 138 | this.timeout(1000 * 60); 139 | promise.consume(read, null, 'dropbox'); 140 | }); 141 | 142 | it('write dropbox', function* () { 143 | this.timeout(1000 * 60); 144 | promise.consume(write, null, 'dropbox'); 145 | }); 146 | 147 | // google drive 148 | 149 | it('complete root path gDrive', function* () { 150 | this.timeout(1000 * 60); 151 | promise.consume(completeRootPath, null, 152 | '#1-1', 'gdrive', '', true, 153 | '#1-2', 'gdrive', 'hello', true, 154 | '#1-3', 'gdrive', '/hello', true, 155 | 156 | '#2-1', 'gdrive', '', false, 157 | '#2-2', 'gdrive', 'hello', false, 158 | '#2-3', 'gdrive', '/hello', false 159 | ); 160 | }); 161 | 162 | it('complete sub path gDrive', function* () { 163 | this.timeout(1000 * 60); 164 | promise.consume(completeSubPath, null, 165 | '#1', 'gdrive', true, 166 | '#2', 'gdrive', false 167 | ); 168 | }); 169 | 170 | it('read gDrive', function* () { 171 | this.timeout(1000 * 60); 172 | promise.consume(read, null, 'gdrive'); 173 | }); 174 | 175 | it('write gDrive', function* () { 176 | this.timeout(1000 * 60); 177 | promise.consume(write, null, 'gdrive'); 178 | }); 179 | 180 | // microsoft onedrive 181 | 182 | it('complete root path onedrive', function* () { 183 | this.timeout(1000 * 60); 184 | promise.consume(completeRootPath, null, 185 | '#1-1', 'onedrive', '', true, 186 | '#1-2', 'onedrive', 'hello', true, 187 | '#1-3', 'onedrive', '/hello', true, 188 | 189 | '#2-1', 'onedrive', '', false, 190 | '#2-2', 'onedrive', 'hello', false, 191 | '#2-3', 'onedrive', '/hello', false 192 | ); 193 | }); 194 | 195 | it('complete sub path onedrive', function* () { 196 | this.timeout(1000 * 60); 197 | promise.consume(completeSubPath, null, 198 | '#1', 'onedrive', true, 199 | '#2', 'onedrive', false 200 | ); 201 | }); 202 | 203 | it('read onedrive', function* () { 204 | this.timeout(1000 * 60); 205 | promise.consume(read, null, 'onedrive'); 206 | }); 207 | 208 | it('write onedrive', function* () { 209 | this.timeout(1000 * 60); 210 | promise.consume(write, null, 'onedrive'); 211 | }); 212 | }; 213 | 214 | -------------------------------------------------------------------------------- /src/wd-tests/launch-and-quit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {By, Key, until} = require('selenium-webdriver'); 4 | const {it} = require('selenium-webdriver/testing'); 5 | 6 | exports.suite = (assert, wasavi, driver) => { 7 | it('existence', function* () { 8 | var wasaviFrame = yield driver.findElement(By.id('wasavi_frame')); 9 | assert.t(wasaviFrame); 10 | }); 11 | 12 | it('app mode existence', function* () { 13 | var a = yield driver.findElement(By.tagName('html')); 14 | assert.eq('1', yield a.getAttribute('data-wasavi-present')); 15 | }); 16 | 17 | it('termination', function* () { 18 | yield wasavi.sendNoWait(':q\n'); 19 | var vanished = yield wasavi.waitTerminate(); 20 | assert.t(vanished); 21 | }); 22 | 23 | it('runtime override settings', function* () { 24 | yield wasavi.sendNoWait(':q\n'); 25 | yield driver.sleep(1000); 26 | 27 | var currentUrl = yield driver.getCurrentUrl(); 28 | yield driver.get(currentUrl + '?ros-test'); 29 | 30 | // phase 1 31 | var wasaviFrame = yield wasavi.invoke(); 32 | assert.t('cannot find wasaviFrame, phase 1', wasaviFrame); 33 | 34 | yield wasaviFrame.click(); 35 | yield wasavi.sendNoWait(':set nu ai\n'); 36 | yield driver.sleep(1000); 37 | yield wasavi.sendNoWait(':wq\n'); 38 | yield driver.sleep(1000); 39 | 40 | // reload 41 | yield driver.navigate().refresh(); 42 | yield driver.sleep(1000); 43 | 44 | // phase 2 45 | wasaviFrame = yield wasavi.invoke(); 46 | assert.t('cannot find wasaviFrame, phase 2', wasaviFrame); 47 | 48 | yield wasaviFrame.click(); 49 | yield wasavi.sendNoWait('a\t\nabc\u001b:wq\n'); 50 | yield driver.sleep(1000); 51 | assert.eq('#1-1', '\t\n\tabc', yield driver.findElement(By.id('t2')).getAttribute('value')); 52 | 53 | yield driver.get(currentUrl); 54 | }); 55 | 56 | it('runtime not override settings', function* () { 57 | // quit 58 | yield wasavi.sendNoWait(':q!\n'); 59 | yield driver.sleep(1000); 60 | 61 | // navigate to new url 62 | var currentUrl = yield driver.getCurrentUrl(); 63 | yield driver.get(currentUrl + '?nooverride'); 64 | 65 | /* 66 | * phase 1 67 | */ 68 | 69 | // #1-1 launch wasavi 70 | var wasaviFrame = yield wasavi.invoke(); 71 | assert.t('cannot find wasaviFrame, phase 1', wasaviFrame); 72 | 73 | // #1-2 set option be different from its default value 74 | yield wasavi.send(':set nu\n'); 75 | 76 | // #1-3 quit 77 | yield wasavi.sendNoWait(':q!\n'); 78 | yield driver.sleep(1000); 79 | 80 | // #1-4 refresh 81 | yield driver.navigate().refresh(); 82 | yield driver.sleep(1000); 83 | 84 | /* 85 | * phase 2 86 | */ 87 | 88 | wasaviFrame = yield wasavi.invoke(); 89 | assert.t('cannot find wasaviFrame, phase 2', wasaviFrame); 90 | 91 | // #2-2 query the state of number option 92 | // and ensure `set nu` at #1-2 is ignored 93 | yield wasavi.send(':set nu?\n'); 94 | assert.eq('nonumber', wasavi.getLastMessage()); 95 | 96 | // #2-3 quit and restore original url 97 | yield wasavi.sendNoWait(':q!\n'); 98 | yield driver.sleep(1000); 99 | yield driver.get(currentUrl); 100 | }); 101 | }; 102 | -------------------------------------------------------------------------------- /src/wd-tests/learning-the-vi-editor-6th.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {By, Key, until, promise} = require('selenium-webdriver'); 4 | const {it, describe} = require('selenium-webdriver/testing'); 5 | 6 | exports.suite = (assert, wasavi, driver) => { 7 | const {ctrln, ctrlt, ctrlw} = assert.shortcuts; 8 | 9 | it('test2_1', function* () { 10 | yield wasavi.setInputModeOfWatchTarget('edit'); 11 | yield wasavi.send('iintroduction'); 12 | assert.eq('#1-1', 'edit', wasavi.getInputMode()); 13 | 14 | yield wasavi.send('\u001b'); 15 | assert.pos('#2-1', 0, 11); 16 | assert.eq('#2-2', 'introduction', wasavi.getValue()); 17 | assert.eq('#2-3', 'introduction', wasavi.getRegister('.')); 18 | }); 19 | 20 | it('test2_2', function* () { 21 | yield wasavi.send( 22 | 'iWith a screen editor you can scroll the\n' + 23 | 'page, move the cursor, delete lines,\n' + 24 | 'and more, while seeing the results of\n' + 25 | 'your edits as you make them.\u001b'); 26 | 27 | yield wasavi.send('3G17|0'); 28 | assert.pos('#1', 2, 0); 29 | 30 | yield wasavi.send('3G17|b'); 31 | assert.pos('#2', 2, 10); 32 | 33 | yield wasavi.send('3G17|2k'); 34 | assert.pos('#3', 0, 16); 35 | 36 | yield wasavi.send('3G17|$'); 37 | assert.pos('#4', 2, 36); 38 | 39 | yield wasavi.send('3G17|2h'); 40 | assert.pos('#5', 2, 14); 41 | 42 | yield wasavi.send('3G17|j'); 43 | assert.pos('#6', 3, 16); 44 | 45 | yield wasavi.send('3G17|2w'); 46 | assert.pos('#7', 2, 27); 47 | 48 | yield wasavi.send('1G1|4l'); 49 | assert.pos('#7', 0, 4); 50 | }); 51 | 52 | it('test2_2_4', function* () { 53 | this.timeout(1000 * 60); 54 | 55 | /* 56 | * 0 10 20 30 40 57 | * *----+----*----+----*----+----*----+----* 58 | * cursor, delete lines, insert chracters, 59 | */ 60 | yield wasavi.send('i\tcursor, delete lines, insert chracters,\u001b'); 61 | 62 | yield wasavi.send('1G1|'); 63 | var wordCols = [1, 7, 9, 16, 21, 23, 30, 39]; 64 | for (var i = 0; i < wordCols.length; i++) { 65 | yield wasavi.send('w'); 66 | assert.pos('w: col ' + wordCols[i], 0, wordCols[i]); 67 | } 68 | 69 | yield wasavi.send('1G1|'); 70 | var bigwordCols = [1, 9, 16, 23, 30]; 71 | for (var i = 0; i < bigwordCols.length; i++) { 72 | yield wasavi.send('W'); 73 | assert.pos('W: col ' + bigwordCols[i], 0, bigwordCols[i]); 74 | } 75 | 76 | yield wasavi.send('1G$'); 77 | var wordColsBackward = [30, 23, 21, 16, 9, 7, 1, 0]; 78 | for (var i = 0; i < wordColsBackward.length; i++) { 79 | yield wasavi.send('b'); 80 | assert.pos('b: col ' + wordColsBackward[i], 0, wordColsBackward[i]); 81 | } 82 | 83 | yield wasavi.send('1G$'); 84 | var bigwordColsBackward = [30, 23, 16, 9, 1, 0]; 85 | for (var i = 0; i < bigwordColsBackward.length; i++) { 86 | yield wasavi.send('B'); 87 | assert.pos('B: col ' + bigwordColsBackward[i], 0, bigwordColsBackward[i]); 88 | } 89 | }); 90 | 91 | it('test2_3', function* () { 92 | /* 93 | With a editor you can scrooll the page, 94 | move the cursor, delete lines, nisret 95 | characters, and more while results of 96 | your edits as you make tham. 97 | Since they allow you to make changes 98 | as you read through a file, much as 99 | you would edit a printed copy, 100 | screen editors are very popular. 101 | */ 102 | 103 | /* 104 | With a screen editor you can scroll the page, 105 | move the cursor, delete lines, insert 106 | characters, and more while seeing the results of 107 | your edits as you make them. 108 | Screen editors are very popular 109 | since they allow you to make changes 110 | as you read through a file, much as 111 | you would edit a printed copy. 112 | */ 113 | yield wasavi.send( 114 | 'iWith a editor you can scrooll the page,\n' + 115 | 'move the cursor, delete lines, nisret\n' + 116 | 'characters, and more while results of\n' + 117 | 'your edits as you make tham.\n' + 118 | 'Since they allow you to make changes\n' + 119 | 'as you read through a file, much as\n' + 120 | 'you would edit a printed copy,\n' + 121 | 'screen editors are very popular.\u001b'); 122 | 123 | yield wasavi.send('1G8|iscreen \u001b'); 124 | yield wasavi.send('/oo\nx'); 125 | yield wasavi.send('2G$bcwinsert\u001b'); 126 | yield wasavi.send('3G4Wiseeing the \u001b'); 127 | yield wasavi.send('4G$Fare'); 128 | yield wasavi.send('5Grs'); 129 | yield wasavi.send('7G$r.'); 130 | yield wasavi.send('8G$x'); 131 | yield wasavi.send('^rS'); 132 | yield wasavi.send('dd2kP'); 133 | 134 | assert.value('#1', 135 | 'With a screen editor you can scroll the page,\n' + 136 | 'move the cursor, delete lines, insert\n' + 137 | 'characters, and more while seeing the results of\n' + 138 | 'your edits as you make them.\n' + 139 | 'Screen editors are very popular\n' + 140 | 'since they allow you to make changes\n' + 141 | 'as you read through a file, much as\n' + 142 | 'you would edit a printed copy.'); 143 | }); 144 | }; 145 | -------------------------------------------------------------------------------- /src/wd-tests/line-input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {By, Key, until, promise} = require('selenium-webdriver'); 4 | const {it, describe} = require('selenium-webdriver/testing'); 5 | 6 | exports.suite = (assert, wasavi, driver) => { 7 | const {ctrln, ctrlt, ctrlw} = assert.shortcuts; 8 | 9 | it('move to top', function* () { 10 | yield wasavi.send(':ersion\u0001v\n'); 11 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 12 | }); 13 | 14 | it('move to top home', function* () { 15 | yield wasavi.send(':ersion' + Key.HOME + 'v\n'); 16 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 17 | }); 18 | 19 | it('move to end', function* () { 20 | yield wasavi.send(':versio\u0001\u0005n\n'); 21 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 22 | }); 23 | 24 | it('move to end end', function* () { 25 | yield wasavi.send(':versio\u0001' + Key.END + 'n\n'); 26 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 27 | }); 28 | 29 | it('back', function* () { 30 | yield wasavi.send(':versin\u0002o\n'); 31 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 32 | }); 33 | 34 | it('back left', function* () { 35 | yield wasavi.send(':versin' + Key.ARROW_LEFT + 'o\n'); 36 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 37 | }); 38 | 39 | it('forward', function* () { 40 | yield wasavi.send(':vrsion\u0001\u0006e\n'); 41 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 42 | }); 43 | 44 | it('forward right', function* () { 45 | yield wasavi.send(':vrsion\u0001' + Key.ARROW_RIGHT + 'e\n'); 46 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 47 | }); 48 | 49 | it('backspace', function* () { 50 | yield wasavi.send(':versiom\u0008n\n'); 51 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 52 | }); 53 | 54 | it('backspace backspace', function* () { 55 | yield wasavi.send(':versiom' + Key.BACK_SPACE + 'n\n'); 56 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 57 | }); 58 | 59 | it('delete', function* () { 60 | yield wasavi.send(':versiooon' + Key.ARROW_LEFT + Key.ARROW_LEFT + Key.DELETE + Key.BACK_SPACE + '\n'); 61 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 62 | }); 63 | 64 | it('next history', function* () { 65 | yield wasavi.send(':wow!\n'); 66 | yield wasavi.send(':version' + Key.ARROW_UP + Key.ARROW_DOWN + '\n'); 67 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 68 | }); 69 | 70 | it('previous history', function* () { 71 | yield wasavi.send(':version\n'); 72 | yield wasavi.send(':set ai?' + Key.ARROW_UP + '\n'); 73 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 74 | }); 75 | 76 | it('delete line', function* () { 77 | yield wasavi.send(':howdy?\u0015version\n'); 78 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 79 | }); 80 | 81 | it('delete word', function* () { 82 | yield wasavi.send(':word', ctrlw, 'version\n'); 83 | assert.t('#1-1', /wasavi/.test(wasavi.getLastMessage())); 84 | }); 85 | 86 | it('complete command name from empty', function* () { 87 | wasavi.setInputModeOfWatchTarget('line_input'); 88 | yield wasavi.send(':\t'); 89 | assert.eq('#1-1', '&', wasavi.getLineInput()); 90 | }); 91 | 92 | it('complete command name with prefix', function* () { 93 | wasavi.setInputModeOfWatchTarget('line_input'); 94 | yield wasavi.send(':ab\t'); 95 | assert.eq('#1-1', 'abbreviate', wasavi.getLineInput()); 96 | }); 97 | 98 | it('complete nested command name from empty', function* () { 99 | wasavi.setInputModeOfWatchTarget('line_input'); 100 | yield wasavi.send(':g/re/\t'); 101 | assert.eq('#1-1', 'g/re/&', wasavi.getLineInput()); 102 | }); 103 | 104 | it('complete nested command name with prefix', function* () { 105 | wasavi.setInputModeOfWatchTarget('line_input'); 106 | yield wasavi.send(':g/re/p\t'); 107 | assert.eq('#1-1', 'g/re/print', wasavi.getLineInput()); 108 | }); 109 | 110 | it('complete 2nd command name from empty', function* () { 111 | wasavi.setInputModeOfWatchTarget('line_input'); 112 | yield wasavi.send(':ver|1,2\t'); 113 | assert.eq('#1-1', 'ver|1,2&', wasavi.getLineInput()); 114 | }); 115 | 116 | it('complete 2nd command name with prefix', function* () { 117 | wasavi.setInputModeOfWatchTarget('line_input'); 118 | yield wasavi.send(':ver|ab\t'); 119 | assert.eq('#1-1', 'ver|abbreviate', wasavi.getLineInput()); 120 | }); 121 | 122 | it('complete option name from empty', function* () { 123 | wasavi.setInputModeOfWatchTarget('line_input'); 124 | yield wasavi.send(':set \t-\u0005'); 125 | assert.eq('#1-1', 'set autoindent-', wasavi.getLineInput()); 126 | }); 127 | 128 | it('complete negative option name from empty', function* () { 129 | wasavi.setInputModeOfWatchTarget('line_input'); 130 | yield wasavi.send(':set no\t-\u0005'); 131 | assert.eq('#1-1', 'set noautoindent-', wasavi.getLineInput()); // must be first boolean option in alphabetical order 132 | }); 133 | 134 | it('complete option name with prefix', function* () { 135 | yield wasavi.send(':set fullsc\t?\n'); 136 | assert.eq('#1-1', 'nofullscreen', wasavi.getLastMessage()); 137 | }); 138 | 139 | it('complete negative option name with prefix', function* () { 140 | yield wasavi.send(':set noautoi\t?\n'); 141 | assert.eq('#1-1', ' autoindent', wasavi.getLastMessage()); 142 | }); 143 | 144 | it('complete abbreviated option name', function* () { 145 | yield wasavi.send(':set nu\t?\n'); 146 | assert.eq('#1-1', 'nonumber', wasavi.getLastMessage()); 147 | }); 148 | 149 | it('complete abbreviated negative option name', function* () { 150 | yield wasavi.send(':set nocub\t?\n'); 151 | assert.eq('#1-1', ' cursorblink', wasavi.getLastMessage()); 152 | }); 153 | 154 | it('complete theme from empty', function* () { 155 | wasavi.setInputModeOfWatchTarget('line_input'); 156 | yield wasavi.send(':set theme=\t'); 157 | assert.t('#1-1', /^set theme=.+/.test(wasavi.getLineInput())); 158 | }); 159 | 160 | it('complete theme with prefix', function* () { 161 | wasavi.setInputModeOfWatchTarget('line_input'); 162 | yield wasavi.send(':set theme=c\t'); 163 | assert.t('#1-1', /^set theme=charcoal/.test(wasavi.getLineInput())); 164 | }); 165 | 166 | it('paste register', function* () { 167 | yield wasavi.send('afoo\nbar\u001b1GyG'); 168 | wasavi.setInputModeOfWatchTarget('line_input'); 169 | yield wasavi.send(':\u0012"'); 170 | assert.eq('#1-1', 'foo\u240abar\u240a', wasavi.getLineInput()); 171 | }); 172 | 173 | it('paste calc register', function* () { 174 | yield wasavi.send('afoo\nbar\nbaz\u001b'); 175 | yield wasavi.send(':\u0012=1+2\np\n'); 176 | assert.eq('#1-1', 'baz', wasavi.getLastMessage()); 177 | }); 178 | 179 | it('paste clipboard', function* () { 180 | yield wasavi.send('afoo\nbar\u001b1G"*yG'); 181 | wasavi.setInputModeOfWatchTarget('line_input'); 182 | yield wasavi.send(':\u0012*'); 183 | assert.eq('#1-1', 'foo\u240abar\u240a', wasavi.getLineInput()); 184 | }); 185 | }; 186 | 187 | -------------------------------------------------------------------------------- /src/wd-tests/scroll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {By, Key, until, promise} = require('selenium-webdriver'); 4 | const {it, describe} = require('selenium-webdriver/testing'); 5 | 6 | exports.suite = (assert, wasavi, driver) => { 7 | const {ctrln, ctrlt, ctrlw} = assert.shortcuts; 8 | 9 | it('scroll up half of view', function* () { 10 | var lines = yield wasavi.makeScrollableBuffer(2.5); 11 | 12 | yield wasavi.send('GH'); 13 | var rowTopBefore = wasavi.getRow(); 14 | yield wasavi.send(':set scroll=0\n\u0015H'); 15 | var rowTopAfter = wasavi.getRow(); 16 | assert.t('#1', Math.abs(Math.abs(rowTopAfter - rowTopBefore) - (lines / 2.0)) <= 1); 17 | 18 | yield wasavi.send('GH'); 19 | rowTopBefore = wasavi.getRow(); 20 | yield wasavi.send(':set scroll=5\n\u0015H'); 21 | rowTopAfter = wasavi.getRow(); 22 | assert.eq('#2', -5, rowTopAfter - rowTopBefore); 23 | 24 | var rowBefore = wasavi.getRow(); 25 | var colBefore = wasavi.getCol(); 26 | yield wasavi.send('d\u0015'); 27 | assert.eq('#3', 'd canceled.', wasavi.getLastMessage()); 28 | assert.t('#4', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 29 | }); 30 | 31 | it('scroll down half of view', function* () { 32 | var lines = yield wasavi.makeScrollableBuffer(2.5); 33 | 34 | yield wasavi.send('gg'); 35 | var rowTopBefore = wasavi.getRow(); 36 | yield wasavi.send(':set scroll=0\n\u0004'); 37 | var rowTopAfter = wasavi.getRow(); 38 | assert.t('#1', Math.abs(Math.abs(rowTopAfter - rowTopBefore) - (lines / 2.0)) <= 1); 39 | 40 | yield wasavi.send('gg'); 41 | rowTopBefore = wasavi.getRow(); 42 | yield wasavi.send(':set scroll=5\n\u0004'); 43 | rowTopAfter = wasavi.getRow(); 44 | assert.eq('#2', 5, rowTopAfter - rowTopBefore); 45 | 46 | var rowBefore = wasavi.getRow(); 47 | var colBefore = wasavi.getCol(); 48 | yield wasavi.send('d\u0004'); 49 | assert.t('#3', wasavi.getLastMessage().length); 50 | assert.t('#4', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 51 | }); 52 | 53 | it('scroll up1Line', function* () { 54 | var lines = yield wasavi.makeScrollableBuffer(2.5); 55 | 56 | yield wasavi.send('G'); 57 | yield wasavi.send('H'); 58 | var rowTopBefore = wasavi.getRow(); 59 | yield wasavi.send('\u0019', 'H'); 60 | var rowTopAfter = wasavi.getRow(); 61 | assert.eq('#1', -1, rowTopAfter - rowTopBefore); 62 | 63 | var rowBefore = wasavi.getRow(); 64 | var colBefore = wasavi.getCol(); 65 | yield wasavi.send('d\u0019'); 66 | assert.t('#2', wasavi.getLastMessage().length); 67 | assert.t('#3', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 68 | }); 69 | 70 | it('scroll down1Line', function* () { 71 | var lines = yield wasavi.makeScrollableBuffer(2.5); 72 | 73 | yield wasavi.send('gg'); 74 | var rowTopBefore = wasavi.getRow(); 75 | yield wasavi.send('\u0005'); 76 | var rowTopAfter = wasavi.getRow(); 77 | assert.eq('#1', 1, rowTopAfter - rowTopBefore); 78 | 79 | var rowBefore = wasavi.getRow(); 80 | var colBefore = wasavi.getCol(); 81 | yield wasavi.send('d\u0005'); 82 | assert.t('#2', wasavi.getLastMessage().length); 83 | assert.t('#3', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 84 | }); 85 | 86 | function* _testScrollUpAlmostView (a) { 87 | var lines = yield wasavi.makeScrollableBuffer(2.5); 88 | 89 | yield wasavi.send('G'); 90 | yield wasavi.send('H'); 91 | var rowTopBefore = wasavi.getRow(); 92 | yield wasavi.send(a); 93 | yield wasavi.send('H'); 94 | var rowTopAfter = wasavi.getRow(); 95 | assert.eq('#1', -(lines - 2), rowTopAfter - rowTopBefore); 96 | 97 | var rowBefore = wasavi.getRow(); 98 | var colBefore = wasavi.getCol(); 99 | yield wasavi.send('d', a); 100 | assert.t('#2', wasavi.getLastMessage().length); 101 | assert.t('#3', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 102 | } 103 | 104 | it('scroll up almost view', function* () { 105 | promise.consume(_testScrollUpAlmostView, null, '\u0002'); 106 | }); 107 | 108 | it('scroll up almost view pageup', function* () { 109 | promise.consume(_testScrollUpAlmostView, null, Key.PAGE_UP); 110 | }); 111 | 112 | function* _testScrollDownAlmostView (a) { 113 | var lines = yield wasavi.makeScrollableBuffer(2.5); 114 | 115 | yield wasavi.send('gg'); 116 | var rowTopBefore = wasavi.getRow(); 117 | yield wasavi.send(a); 118 | var rowTopAfter = wasavi.getRow(); 119 | assert.eq('#1', lines - 2, rowTopAfter - rowTopBefore); 120 | 121 | var rowBefore = wasavi.getRow(); 122 | var colBefore = wasavi.getCol(); 123 | yield wasavi.send('d', a); 124 | assert.t('#2', wasavi.getLastMessage().length); 125 | assert.t('#3', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 126 | } 127 | 128 | it('scroll down almost view', function* () { 129 | promise.consume(_testScrollDownAlmostView, null, '\u0006'); 130 | }); 131 | 132 | it('scroll down almost view pagedown', function* () { 133 | promise.consume(_testScrollDownAlmostView, null, Key.PAGE_DOWN); 134 | }); 135 | 136 | it('screen adjust top', function* () { 137 | var lines = yield wasavi.makeScrollableBuffer(2); 138 | yield wasavi.send('gg10G'); 139 | 140 | yield wasavi.send('z', Key.ENTER); 141 | assert.t('#1', wasavi.getLastMessage() == ''); 142 | assert.t('#2', 143 | wasavi.getTopRow() == wasavi.getRow() && 144 | wasavi.getTopCol() == wasavi.getCol()); 145 | }); 146 | 147 | function* _testScreenAdjustCenter (a) { 148 | var lines = yield wasavi.makeScrollableBuffer(2); 149 | yield wasavi.send('gg'); 150 | yield wasavi.send(Math.floor(lines * 0.75), 'G'); 151 | yield wasavi.send('z', a); 152 | assert.t('#1', wasavi.getLastMessage() == ''); 153 | 154 | var actualTopRow = wasavi.getTopRow(); 155 | var idealTopRow = wasavi.getRow() - (lines / 2.0); 156 | assert.t('#2', Math.abs(idealTopRow - actualTopRow) <= 1); 157 | } 158 | 159 | it('screen adjust center', function* () { 160 | promise.consume(_testScreenAdjustCenter, null, '.'); 161 | }); 162 | 163 | it('screen adjust center z', function* () { 164 | promise.consume(_testScreenAdjustCenter, null, 'z'); 165 | }); 166 | 167 | it('screen adjust bottom', function* () { 168 | var lines = yield wasavi.makeScrollableBuffer(2); 169 | yield wasavi.send('gg'); 170 | yield wasavi.send('20G'); 171 | yield wasavi.send('z-'); 172 | assert.t('#1', wasavi.getLastMessage() == ''); 173 | 174 | var actualTopRow = wasavi.getTopRow(); 175 | var idealTopRow = wasavi.getRow() - (lines - 1); 176 | assert.t('#2', Math.abs(idealTopRow - actualTopRow) <= 1); 177 | }); 178 | 179 | it('screen adjust other', function* () { 180 | var lines = yield wasavi.makeScrollableBuffer(); 181 | var rowBefore = wasavi.getRow(); 182 | var colBefore = wasavi.getCol(); 183 | yield wasavi.send('zX'); 184 | assert.t('#1', wasavi.getLastMessage().length); 185 | assert.t('#2', rowBefore == wasavi.getRow() && colBefore == wasavi.getCol()); 186 | }); 187 | }; 188 | -------------------------------------------------------------------------------- /src/wd-tests/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * simple http server 4 | * 5 | * @see http://stackoverflow.com/questions/6084360/using-node-js-as-a-simple-web-server 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const http = require('http'), 11 | url = require('url'), 12 | path = require('path'), 13 | fs = require('fs'); 14 | 15 | const contentTypesByExtension = { 16 | '.html': 'text/html', 17 | '.css': 'text/css', 18 | '.js': 'text/javascript' 19 | }; 20 | 21 | function handleRequest (filename, response) { 22 | fs.exists(filename, (exists) => { 23 | if(!exists) { 24 | response.writeHead(404, {'Content-Type': 'text/plain'}); 25 | response.write('404 Not Found\n'); 26 | response.end(); 27 | return; 28 | } 29 | 30 | if (fs.statSync(filename).isDirectory()) { 31 | handleRequest(filename + '/index.html', response); 32 | return; 33 | } 34 | 35 | fs.readFile(filename, 'binary', (err, file) => { 36 | if (err) { 37 | response.writeHead(500, {'Content-Type': 'text/plain'}); 38 | response.write(err + '\n'); 39 | response.end(); 40 | return; 41 | } 42 | 43 | var headers = {}; 44 | var contentType = contentTypesByExtension[path.extname(filename)]; 45 | if (contentType) { 46 | headers['Content-Type'] = contentType; 47 | } 48 | response.writeHead(200, headers); 49 | response.write(file, 'binary'); 50 | response.end(); 51 | }); 52 | }); 53 | } 54 | 55 | function start (basedir, port, options) { 56 | options || (options = {}); 57 | var server = http.createServer((request, response) => { 58 | try { 59 | var uri = url.parse(request.url).pathname, 60 | filename = path.join(process.cwd(), basedir, uri); 61 | 62 | //console.log(uri + '\t' + filename); 63 | 64 | if (uri == '/shutdown') { 65 | response.writeHead(200, {'Content-Type': 'text/plain'}); 66 | response.write('Bye!\n'); 67 | response.end(); 68 | setTimeout(() => { 69 | server.close(); 70 | server = null; 71 | if (!('noexit' in options) || !options.noexit) { 72 | process.exit(0); 73 | } 74 | }, 100); 75 | return; 76 | } 77 | 78 | handleRequest(filename, response); 79 | } 80 | catch (ex) { 81 | console.log('exception in www server: ' + ex); 82 | } 83 | }); 84 | server.on('error', err => { 85 | console.log('exception: ' + err); 86 | process.exit(1); 87 | }); 88 | server.listen(parseInt(port, 10), () => { 89 | if (!('silent' in options) || !options.silent) { 90 | console.log('started on http://localhost:' + port + '/'); 91 | console.log(' - base directory: ' + basedir); 92 | console.log(' - Press CTRL+C or access /shutdown to shutdown'); 93 | } 94 | }); 95 | } 96 | 97 | try { 98 | if (path.basename(process.argv[1]) == path.basename(__filename)) { 99 | start(process.argv[3] || 'www', process.argv[2] || 8888); 100 | } 101 | else { 102 | exports.start = start; 103 | } 104 | } 105 | catch (ex) { 106 | console.log('exception: ' + ex); 107 | process.exit(1); 108 | } 109 | 110 | // vim:set ts=4 sw=4 fenc=UTF-8 ff=unix ft=javascript fdm=marker fmr=<<<,>>> : 111 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | AddType text/cache-manifest .appcache 2 | -------------------------------------------------------------------------------- /www/authorized.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 22 | 23 | 24 | 25 | Authorized! 26 | 27 | 28 | 45 | 46 | 47 |
    48 |

    Authorized!

    49 |

    Succeeded to connect to the online storage.

    50 |
    51 | 52 | 53 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akahuku/wasavi/e88948587ac5f0da56ad36bf9a7a0b76e64eb67e/www/favicon.ico -------------------------------------------------------------------------------- /www/icon016.png: -------------------------------------------------------------------------------- 1 | ../src/chrome/images/icon016.png -------------------------------------------------------------------------------- /www/icon048.png: -------------------------------------------------------------------------------- 1 | ../src/chrome/images/icon048.png -------------------------------------------------------------------------------- /www/icon128.png: -------------------------------------------------------------------------------- 1 | ../src/chrome/images/icon128.png -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 22 | 23 | 24 | 25 | wasavi - online vi editor 26 | 27 | 28 | 29 | 30 | 31 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /www/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Wasavi", 3 | "name": "Wasavi - online vi editor", 4 | "icons": [ 5 | { 6 | "src": "icon016.png", 7 | "sizes": "16x16", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "icon048.png", 12 | "sizes": "48x48", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "icon128.png", 17 | "sizes": "128x128", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": "/", 22 | "display": "standalone" 23 | } 24 | -------------------------------------------------------------------------------- /www/test_frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | wasavi test frame 6 | 62 | 202 | 203 | 204 |

    wasavi test frame

    205 |
    206 |
    207 |
    208 |
    209 | 210 |
    211 |
    212 |
    213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 |
    221 |
    222 |
    223 | 224 |
    225 |
    226 | 227 | 228 | -------------------------------------------------------------------------------- /www/wasavi.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # last-modified: 2016-01-20 00:03:00 3 | 4 | CACHE: 5 | favicon.ico 6 | index.html 7 | authorized.html 8 | 9 | NETWORK: 10 | * 11 | --------------------------------------------------------------------------------