diff --git a/.eslintrc.js b/.eslintrc.js index 3663e2f317d..40331166ab1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,6 +7,7 @@ module.exports = { "no-empty": 1, "no-invalid-regexp": 2, "no-regex-spaces": 2, + "no-unsafe-negation": 1, "valid-jsdoc": 0, "valid-typeof": 2, // http://eslint.org/docs/rules/#best-practices @@ -23,6 +24,7 @@ module.exports = { "no-new-wrappers": 2, "no-new": 2, "no-proto": 2, + "no-redeclare": 1, "no-script-url": 2, "wrap-iife": [2, "outside"], // http://eslint.org/docs/rules/#strict-mode diff --git a/Gruntfile.js b/Gruntfile.js index a6b638113b6..7b08d8738a1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -268,21 +268,20 @@ module.exports = function (grunt) { ] }, watch: { - all : { - files: ['**/*', '!**/node_modules/**'], - tasks: ['eslint'] - }, - grunt : { - files: ['<%= meta.grunt %>', 'tasks/**/*'], + grunt: { + files: ['<%= meta.grunt %>'], tasks: ['eslint:grunt'] }, - src : { - files: ['<%= meta.src %>', 'src/**/*'], + src: { + files: ['<%= meta.src %>'], tasks: ['eslint:src'] }, - test : { - files: ['<%= meta.test %>', 'test/**/*'], + test: { + files: ['<%= meta.test %>'], tasks: ['eslint:test'] + }, + options: { + spawn: false } }, /* FIXME (jasonsanjose): how to handle extension tests */ diff --git a/build.json b/build.json index b2d5d0eda9a..908032018f5 100644 --- a/build.json +++ b/build.json @@ -1,6 +1,6 @@ { - "version": "release-1.9-prerelease-2", - "title" : "Brackets 1.9 Stable Pre-release for community testing", - "description" : "This is a Brackets 1.9 pre-release build.", + "version": "release-1.10-prerelease-3", + "title" : "Brackets 1.10 Pre-release for community testing", + "description" : "This is a Brackets 1.10 3rd pre-release build. This release removes the utf-8 encoding limitation, adds native menus for Linux, provides backward/forward navigation in edit history, Search History with UI, enable/disable default exetensions from extension manager, pseudo selector and AtRules hints in CSS, CSS hints in style attribute value for HTML documents.", "prerelease": true } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a96f2cea66a..e5df87e2b5a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "Brackets", - "version": "1.9.0-0", + "version": "1.10.0-0", "dependencies": { "abbrev": { "version": "1.1.0", @@ -190,6 +190,12 @@ "from": "aws4@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz" }, + "babel-code-frame": { + "version": "6.22.0", + "from": "babel-code-frame@>=6.16.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "dev": true + }, "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", @@ -249,6 +255,26 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "dev": true }, + "body-parser": { + "version": "1.14.2", + "from": "body-parser@>=1.14.0 <1.15.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "dev": true, + "dependencies": { + "iconv-lite": { + "version": "0.4.13", + "from": "iconv-lite@0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "dev": true + }, + "qs": { + "version": "5.2.0", + "from": "qs@5.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "dev": true + } + } + }, "boom": { "version": "2.10.1", "from": "boom@>=2.0.0 <3.0.0", @@ -340,6 +366,12 @@ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", "dev": true }, + "bytes": { + "version": "2.2.0", + "from": "bytes@2.2.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "dev": true + }, "caller-path": { "version": "0.1.0", "from": "caller-path@>=0.1.0 <0.2.0", @@ -505,6 +537,12 @@ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", "dev": true }, + "content-type": { + "version": "1.0.2", + "from": "content-type@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "dev": true + }, "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0", @@ -577,10 +615,18 @@ "dev": true }, "debug": { - "version": "0.7.4", - "from": "debug@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "dev": true + "version": "2.2.0", + "from": "debug@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "dev": true, + "dependencies": { + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "dev": true + } + } }, "decamelize": { "version": "1.2.0", @@ -622,6 +668,12 @@ "from": "delayed-stream@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" }, + "depd": { + "version": "1.1.0", + "from": "depd@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "dev": true + }, "des.js": { "version": "1.0.0", "from": "des.js@>=1.0.0 <2.0.0", @@ -635,9 +687,9 @@ "dev": true }, "doctrine": { - "version": "1.5.0", - "from": "doctrine@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "version": "2.0.0", + "from": "doctrine@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", "dev": true }, "domain-browser": { @@ -652,6 +704,12 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "optional": true }, + "ee-first": { + "version": "1.1.1", + "from": "ee-first@1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "dev": true + }, "elliptic": { "version": "6.4.0", "from": "elliptic@>=6.0.0 <7.0.0", @@ -695,16 +753,68 @@ "dev": true }, "es6-map": { - "version": "0.1.4", + "version": "0.1.5", "from": "es6-map@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "from": "d@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "dev": true + }, + "es5-ext": { + "version": "0.10.15", + "from": "es5-ext@>=0.10.14 <0.11.0", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.15.tgz", + "dev": true + }, + "es6-iterator": { + "version": "2.0.1", + "from": "es6-iterator@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", + "dev": true + }, + "es6-symbol": { + "version": "3.1.1", + "from": "es6-symbol@>=3.1.1 <3.2.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "dev": true + } + } }, "es6-set": { - "version": "0.1.4", - "from": "es6-set@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz", - "dev": true + "version": "0.1.5", + "from": "es6-set@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "from": "d@1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "dev": true + }, + "es5-ext": { + "version": "0.10.15", + "from": "es5-ext@~0.10.14", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.15.tgz", + "dev": true + }, + "es6-iterator": { + "version": "2.0.1", + "from": "es6-iterator@~2.0.1", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", + "dev": true + }, + "es6-symbol": { + "version": "3.1.1", + "from": "es6-symbol@3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "dev": true + } + } }, "es6-symbol": { "version": "3.1.0", @@ -713,10 +823,36 @@ "dev": true }, "es6-weak-map": { - "version": "2.0.1", + "version": "2.0.2", "from": "es6-weak-map@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "from": "d@1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "dev": true + }, + "es5-ext": { + "version": "0.10.15", + "from": "es5-ext@^0.10.14", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.15.tgz", + "dev": true + }, + "es6-iterator": { + "version": "2.0.1", + "from": "es6-iterator@^2.0.1", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", + "dev": true + }, + "es6-symbol": { + "version": "3.1.1", + "from": "es6-symbol@^3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "dev": true + } + } }, "escape-string-regexp": { "version": "1.0.5", @@ -730,9 +866,9 @@ "dev": true }, "eslint": { - "version": "2.13.1", - "from": "eslint@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", + "version": "3.18.0", + "from": "eslint@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.18.0.tgz", "dev": true, "dependencies": { "argparse": { @@ -758,6 +894,12 @@ "from": "js-yaml@>=3.5.1 <4.0.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.2.tgz", "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "from": "strip-bom@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "dev": true } } }, @@ -773,6 +915,12 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", "dev": true }, + "esquery": { + "version": "1.0.0", + "from": "esquery@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "dev": true + }, "esrecurse": { "version": "4.1.0", "from": "esrecurse@>=4.1.0 <5.0.0", @@ -800,10 +948,24 @@ "dev": true }, "event-emitter": { - "version": "0.3.4", - "from": "event-emitter@>=0.3.4 <0.4.0", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz", - "dev": true + "version": "0.3.5", + "from": "event-emitter@>=0.3.5 <0.4.0", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "from": "d@1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "dev": true + }, + "es5-ext": { + "version": "0.10.15", + "from": "es5-ext@~0.10.14", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.15.tgz", + "dev": true + } + } }, "eventemitter2": { "version": "0.4.14", @@ -867,9 +1029,9 @@ "dev": true }, "faye-websocket": { - "version": "0.4.4", - "from": "faye-websocket@>=0.4.3 <0.5.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.4.4.tgz", + "version": "0.10.0", + "from": "faye-websocket@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", "dev": true }, "figures": { @@ -879,9 +1041,9 @@ "dev": true }, "file-entry-cache": { - "version": "1.3.1", - "from": "file-entry-cache@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", + "version": "2.0.0", + "from": "file-entry-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", "dev": true }, "filename-regex": { @@ -1073,6 +1235,20 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", "dev": true }, + "globule": { + "version": "1.1.0", + "from": "globule@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.1.0.tgz", + "dev": true, + "dependencies": { + "lodash": { + "version": "4.16.6", + "from": "lodash@>=4.16.4 <4.17.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", + "dev": true + } + } + }, "graceful-fs": { "version": "4.1.11", "from": "graceful-fs@>=4.1.2 <5.0.0", @@ -1229,15 +1405,35 @@ "dev": true }, "grunt-contrib-watch": { - "version": "0.4.3", - "from": "grunt-contrib-watch@0.4.3", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-0.4.3.tgz", - "dev": true + "version": "1.0.0", + "from": "grunt-contrib-watch@1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", + "dev": true, + "dependencies": { + "async": { + "version": "1.5.2", + "from": "async@>=1.5.0 <2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "dev": true + }, + "gaze": { + "version": "1.1.2", + "from": "gaze@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", + "dev": true + }, + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.10.1 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "dev": true + } + } }, "grunt-eslint": { - "version": "18.1.0", - "from": "grunt-eslint@18.1.0", - "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-18.1.0.tgz", + "version": "19.0.0", + "from": "grunt-eslint@19.0.0", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-19.0.0.tgz", "dev": true }, "grunt-jasmine-node": { @@ -1400,6 +1596,12 @@ "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-0.5.6.tgz", "dev": true }, + "http-errors": { + "version": "1.3.1", + "from": "http-errors@>=1.3.1 <1.4.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "dev": true + }, "http-signature": { "version": "1.1.1", "from": "http-signature@>=1.1.0 <1.2.0", @@ -1438,9 +1640,9 @@ "dev": true }, "ignore": { - "version": "3.2.4", - "from": "ignore@>=3.1.2 <4.0.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.2.4.tgz", + "version": "3.2.6", + "from": "ignore@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.2.6.tgz", "dev": true }, "image-size": { @@ -1675,6 +1877,12 @@ "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", "optional": true }, + "js-tokens": { + "version": "3.0.1", + "from": "js-tokens@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", + "dev": true + }, "js-yaml": { "version": "2.0.5", "from": "js-yaml@>=2.0.5 <2.1.0", @@ -1790,6 +1998,12 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "dev": true }, + "livereload-js": { + "version": "2.2.2", + "from": "livereload-js@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", + "dev": true + }, "load-grunt-tasks": { "version": "3.5.0", "from": "load-grunt-tasks@3.5.0", @@ -1831,6 +2045,12 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", "dev": true }, + "media-typer": { + "version": "0.3.0", + "from": "media-typer@0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "dev": true + }, "memory-fs": { "version": "0.4.1", "from": "memory-fs@>=0.4.1 <0.5.0", @@ -1922,6 +2142,12 @@ "from": "nan@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz" }, + "natural-compare": { + "version": "1.4.0", + "from": "natural-compare@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "dev": true + }, "node-libs-browser": { "version": "2.0.0", "from": "node-libs-browser@>=2.0.0 <3.0.0", @@ -1933,20 +2159,6 @@ "from": "nopt@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" }, - "noptify": { - "version": "0.0.3", - "from": "noptify@latest", - "resolved": "https://registry.npmjs.org/noptify/-/noptify-0.0.3.tgz", - "dev": true, - "dependencies": { - "nopt": { - "version": "2.0.0", - "from": "nopt@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.0.0.tgz", - "dev": true - } - } - }, "normalize-package-data": { "version": "2.3.6", "from": "normalize-package-data@>=2.3.2 <3.0.0", @@ -3488,6 +3700,12 @@ "from": "object.omit@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz" }, + "on-finished": { + "version": "2.3.0", + "from": "on-finished@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "dev": true + }, "once": { "version": "1.4.0", "from": "once@>=1.3.0 <2.0.0", @@ -3588,6 +3806,12 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "dev": true }, + "parseurl": { + "version": "1.3.1", + "from": "parseurl@>=1.3.0 <1.4.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "dev": true + }, "path-browserify": { "version": "0.0.0", "from": "path-browserify@0.0.0", @@ -3611,6 +3835,12 @@ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "dev": true }, + "path-parse": { + "version": "1.0.5", + "from": "path-parse@>=1.0.5 <2.0.0", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "dev": true + }, "path-type": { "version": "1.1.0", "from": "path-type@>=1.0.0 <2.0.0", @@ -3888,6 +4118,26 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz", "dev": true }, + "raw-body": { + "version": "2.1.7", + "from": "raw-body@>=2.1.5 <2.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "dev": true, + "dependencies": { + "bytes": { + "version": "2.4.0", + "from": "bytes@2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "dev": true + }, + "iconv-lite": { + "version": "0.4.13", + "from": "iconv-lite@0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "dev": true + } + } + }, "read-pkg": { "version": "1.1.0", "from": "read-pkg@>=1.0.0 <2.0.0", @@ -3916,6 +4166,20 @@ "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", "dev": true }, + "rechoir": { + "version": "0.6.2", + "from": "rechoir@>=0.6.2 <0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "dev": true, + "dependencies": { + "resolve": { + "version": "1.3.2", + "from": "resolve@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.2.tgz", + "dev": true + } + } + }, "regex-cache": { "version": "0.4.3", "from": "regex-cache@>=0.4.2 <0.5.0", @@ -4068,9 +4332,9 @@ "dev": true }, "shelljs": { - "version": "0.6.1", - "from": "shelljs@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", + "version": "0.7.7", + "from": "shelljs@>=0.7.5 <0.8.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz", "dev": true }, "sigmund": { @@ -4138,6 +4402,12 @@ } } }, + "statuses": { + "version": "1.3.1", + "from": "statuses@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "dev": true + }, "stream-browserify": { "version": "2.0.1", "from": "stream-browserify@>=2.0.1 <3.0.0", @@ -4178,9 +4448,9 @@ "dev": true }, "strip-json-comments": { - "version": "1.0.4", - "from": "strip-json-comments@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "version": "2.0.1", + "from": "strip-json-comments@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "dev": true }, "supports-color": { @@ -4250,15 +4520,15 @@ "dev": true }, "tiny-lr": { - "version": "0.0.4", - "from": "tiny-lr@0.0.4", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.0.4.tgz", + "version": "0.2.1", + "from": "tiny-lr@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", "dev": true, "dependencies": { "qs": { - "version": "0.5.6", - "from": "qs@>=0.5.2 <0.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", + "version": "5.1.0", + "from": "qs@>=5.1.0 <5.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", "dev": true } } @@ -4325,6 +4595,12 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "dev": true }, + "type-is": { + "version": "1.6.14", + "from": "type-is@>=1.6.10 <1.7.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.14.tgz", + "dev": true + }, "typedarray": { "version": "0.0.6", "from": "typedarray@>=0.0.6 <0.0.7", @@ -4369,6 +4645,12 @@ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", "dev": true }, + "unpipe": { + "version": "1.0.0", + "from": "unpipe@1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "dev": true + }, "url": { "version": "0.11.0", "from": "url@>=0.11.0 <0.12.0", @@ -4476,6 +4758,18 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.1.5.tgz", "dev": true }, + "websocket-driver": { + "version": "0.6.5", + "from": "websocket-driver@>=0.5.1", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "dev": true + }, + "websocket-extensions": { + "version": "0.1.1", + "from": "websocket-extensions@>=0.1.1", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "dev": true + }, "which": { "version": "1.0.9", "from": "which@>=1.0.5 <1.1.0", diff --git a/package.json b/package.json index 35de9fe30e1..3a1a5b4746f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Brackets", - "version": "1.9.0-0", - "apiVersion": "1.9.0", + "version": "1.10.0-0", + "apiVersion": "1.10.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" @@ -35,8 +35,8 @@ "grunt-cli": "0.1.9", "phantomjs": "1.9.18", "grunt-lib-phantomjs": "0.3.0", - "grunt-eslint": "18.1.0", - "grunt-contrib-watch": "0.4.3", + "grunt-eslint": "19.0.0", + "grunt-contrib-watch": "1.0.0", "grunt-contrib-jasmine": "0.4.2", "grunt-template-jasmine-requirejs": "0.1.0", "grunt-contrib-cssmin": "0.6.0", @@ -68,4 +68,4 @@ "url": "https://github.com/adobe/brackets/blob/master/LICENSE" } ] -} +} \ No newline at end of file diff --git a/samples/ja/Getting Started/index.html b/samples/ja/Getting Started/index.html index 3b1f31734ee..bf6f491fef1 100644 --- a/samples/ja/Getting Started/index.html +++ b/samples/ja/Getting Started/index.html @@ -14,7 +14,7 @@

BRACKETS をはじめる前に

まずはこのガイドからスタート

@@ -22,7 +22,7 @@

まずはこのガイドからスタート

Brackets は、新しいタイプのエディターです。 @@ -30,7 +30,7 @@

まずはこのガイドからスタート

Brackets のプロジェクト

@@ -45,7 +45,7 @@

Brackets のプロジェクト

CSS と JavaScript のクイック編集

@@ -71,7 +71,7 @@

CSS と JavaScript のクイック編集

HTML および CSS の編集結果をブラウザーでライブプレビュー

@@ -114,7 +114,7 @@

拡張機能でさらに補強

Brackets プロジェクトに参加

@@ -155,4 +155,4 @@

Brackets プロジェクトに参加

[:::::::::::::: ::::::::::::::] [[[[[[[[[[[[[[[ ]]]]]]]]]]]]]]] ---> \ No newline at end of file +--> diff --git a/samples/zh-cn/Getting Started/index.html b/samples/zh-cn/Getting Started/index.html new file mode 100644 index 00000000000..85e7ed810d8 --- /dev/null +++ b/samples/zh-cn/Getting Started/index.html @@ -0,0 +1,193 @@ + + + + + + + BRACKETS 入门 + + + + + +

BRACKETS 入门

+

让我为你好好介绍!

+ + + +

+ 欢迎使用 Brackets,这是个很懂网页设计的现代化开放原始码程式编辑器。 + 轻巧又不失威力,整合多项视觉化的编辑功能,在需要时提供您适当的协助。 +

+ + +

+ Brackets 与众不同。 + Brackets 提供「快速编辑」、「即时预览」等别的编辑器没有的独家功能。 + 而且 Brackets 是用 JavaScript, HTML 及 CSS 写出来的。这代表大多数使用 Brackets 的人都有能力修改及扩充它。 + 事实上,Brackets 本身就是我们用 Brackets 一天天打造出来的。 + 如果您想学会如何使用这些功能,请继续看下去。 +

+ + + +

Brackets 中的「专案」

+

+ 只要开启包含您程式码的资料夹,就能使用 Brackets 来编辑。 + Brackets 会将目前开启的资料夹视为一个「专案」,「程式提示」、「即时预览」及「快速编辑」等功能都只会参考到专案裡的档案。 +

+ + + 要是您已经准备好关掉这个范例专案,开始编辑自已的程式,可以使用左边侧栏的下拉式选单切换资料夹。 + 现在应该是选到「Getting Started」,也就是您看的这份文件所在的资料夹。 + 按一下下拉式选单,点选「开启资料夹…」选项,就能开启您自已的资料夹。 + + 之后您也可以透过同样的下拉式选单切回开启过的资料夹,包含这个范例专案。 + + + +

CSS 及 JavaScript 快速编辑

+

+ 别再因为不断切换档案而一直分神失焦了。编辑 HTML 时,按下 Cmd/Ctrl + E + 快速键就地开启编辑器,秀出所有相关的 CSS 规则。 + 调好 CSS 样式后按 ESC 马上就能回到 HTML 继续编辑。 + 此外,也可以放手让那些 CSS 规则一直开在 HTML 编辑器裡。 + 只要在快速编辑器的范围外按下 ESC 键,就能关掉所有快速编辑器。 + 快速编辑也能找到定义在 LESS 及 SCSS 档案中的规则,就算是巢状规则也没问题。 +

+ + + 想亲身体验吗? 把游标移到上面的 标籤中,按下 Cmd/Ctrl + E。 + 您应该就会看到 CSS 快速编辑器出现在上方,显示出所有套用到的 CSS 规则。 + 快速编辑功能也支援 class 及 id 属性。搭配 LESS 或 SCSS 档嘛会通喔。 + + 您也可以透过这个方式新增规则。在上方随便一个 标籤上点一下,按 Cmd/Ctrl + E。 + 可以看到它上面并没有任何 CSS 规则,但您可以按一下「新增规则」按钮,就会新增 规则。 + + + + 使用 CSS 快速编辑的画面撷图 + + +

+ 您也能使用相同的快速键编辑其他东西,例如 JavaScript 函式、CSS 色彩、CSS 动画计时函式等,持续增加中。 +

+

+ 目前还不能在快速编辑器中巢状开启其他快速编辑器,只有游标在主编辑器时才能开快速编辑功能。 +

+ + +

在浏览器裡即时预览 HTML 及 CSS 变更

+

+ 有一种舞叫做「存档再重新载入探戈」,我们跳了好多年,您听过吗? + 就是在编辑器裡改一改东西,储存好,马上再切过去浏览器,按「重新整理」后才能真正的看到结果,超鸟的! + 用 Brackets,您永远不必再这麽「跳」。 +

+

+ Brackets 会跟您本机的浏览器即时连线,在您修改的同时将 HTML 及 CSS 内容更新过去! + 说不定活在 21 世纪的您已经用浏览器提供的开发者工具做过类似的事了。 + 但是用 Brackets,您不用再手动把总算是会动的程式複製贴回编辑器。 + 您的程式虽然是跑在浏览器上,但是所有的血与肉都还是在编辑器裡啊! +

+ +

即时突显 HTML 元素及 CSS 规则

+

+ Brackets 让您更容易看到 HTML 及 CSS 的修改会对页面造成什麽影响。 + 当游标停在 CSS 规则上时,Brackets 会在浏览器裡将所有会受影响的元素突显出来。 + 编辑 HTML 档案时,Brackets 也会在浏览器中突显对应的 HTML 元素。 +

+ + + 如果您安装了 Google Chrome,马上就可以试看看。 + 按一下 Brackets 视窗右上角的闪电图示,或是按 Cmd/Ctrl + Alt + P。 + 当即时预览功能在 HTML 档案上启用后,所有连结到的 CSS 档案也都可以马上编辑马上生效。 + Brackets 与您的浏览器建立连线时,图示会由灰转金。 + + 就是现在,把游标移到上面的 标籤。注意看 Chrome 在图片上显示的蓝色框。 + 接下来,按 Cmd/Ctrl + E 开启相关的 CSS 规则定义。 + 试著将框线 (border) 值由 10px 改成 20px,或将背景色 (background-color) 由透明 "transparent" 改成 "hotpink"。 + 如果您把 Brackets 跟浏览器并排放好,就能看到所有异动都直接反应在浏览器上了。酷吧?! + + +

+ 目前 Brackets 只能即时预览 HTML 及 CSS。不过,储存修改过的 JavaScript 档案时也会自动重新载入页面。 + 我们正在努力让即时预览功能支援 JavaScript。 + 此外,即时预览现在只能在 Google Chrome 上执行,我们希望将来能支援所有主流的浏览器。 +

+ +

快速检视

+

+ 为了那些记不得色彩十六进位值或是 RGB 值的人,Brackets 能快速又简单的让您看见色彩的真相。 + 不管在 CSS 或 HTML 中,只要将滑鼠游标移到任何色彩值或是渐变色上,Brackets 就会自动显示预览。 + 对图片也同样有用,在 Brackets 裡将滑鼠游标移到图片连结上,就会自动显示预览缩图。 +

+ + + 自已试试快速检视,只要将游标移到这份文件最上方的 标籤上,按下 Cmd/Ctrl + E + 开启 CSS 快速编辑器,将滑鼠游标移到 CSS 上的任何一个色彩值上就能看到。 + 想要预览渐变色,您也可以在 标籤上开启 CSS 快速编辑器,移到随便一个背景图片 (background-image) 值就能看到。 + 要试图片预览,则是将游标移到前几段提到的画面撷图上就能看到。 + + +

还不够吗? 安装扩充功能吧!

+

+ 除了 Brackets 内建的这些好物外,我们那深具规模,且日益状大的开发者社群已经写出了数百个扩充功能。 + 如果您觉得 Brackets 少了什麽,说不定早就有人写好扩充功能了。 + 点一下 档案 > 扩充功能管理员...,再点一下「可使用」页籤,就能浏览或搜寻扩充功能清单。 + 一旦找到想要的扩充功能,按一下后面的「安装」按钮就可以了。 +

+ + +

一起参与

+

+ Brackets 专案是开放原始码的。世界各地的网页开发者贡献一己之力,只为打造出更好的程式编辑器。 + 也有不少人正在开发扩充功能,让 Brackets 更强大。 + 告诉我们您的想法,分享您的构想,或是直接为本专案做点事吧。 +

+ + + + + diff --git a/samples/zh-cn/Getting Started/main.css b/samples/zh-cn/Getting Started/main.css new file mode 100644 index 00000000000..225fc8967f1 --- /dev/null +++ b/samples/zh-cn/Getting Started/main.css @@ -0,0 +1,60 @@ +html { + background: #e6e9e9; + background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); + -webkit-font-smoothing: antialiased; +} + +body { + background: #fff; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); + color: #545454; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; + margin: 0 auto; + max-width: 800px; + padding: 2em 2em 4em; +} + +h1, h2, h3, h4, h5, h6 { + color: #222; + font-weight: 700; + line-height: 1.3; +} + +h2 { + margin-top: 1.3em; +} + +a { + color: #0083e8; +} + +b, strong { + font-weight: 700; +} + +samp { + display: none; +} + +img { + animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; + background: transparent; + border: 10px solid rgba(0, 0, 0, 0.12); + border-radius: 4px; + display: block; + margin: 1.3em auto; + max-width: 95%; +} + +@keyframes colorize { + 0% { + -webkit-filter: grayscale(100%); + filter: grayscale(100%); + } + 100% { + -webkit-filter: grayscale(0%); + filter: grayscale(0%); + } +} diff --git a/samples/zh-cn/Getting Started/screenshots/quick-edit.png b/samples/zh-cn/Getting Started/screenshots/quick-edit.png new file mode 100644 index 00000000000..82a984b5255 Binary files /dev/null and b/samples/zh-cn/Getting Started/screenshots/quick-edit.png differ diff --git a/src/LiveDevelopment/Agents/RemoteAgent.js b/src/LiveDevelopment/Agents/RemoteAgent.js index 6bb71e7c839..7c084ed9bab 100644 --- a/src/LiveDevelopment/Agents/RemoteAgent.js +++ b/src/LiveDevelopment/Agents/RemoteAgent.js @@ -131,7 +131,7 @@ define(function RemoteAgent(require, exports, module) { _stopKeepAliveInterval(); // inject RemoteFunctions - var command = "window._LD=" + RemoteFunctions + "(" + LiveDevelopment.config.experimental + "," + PreferencesManager.get("livedev.wsPort") + ");"; + var command = "window._LD=" + RemoteFunctions + "(" + JSON.stringify(LiveDevelopment.config) + "," + PreferencesManager.get("livedev.wsPort") + ");"; Inspector.Runtime.evaluate(command, function onEvaluate(response) { if (response.error || response.wasThrown) { diff --git a/src/LiveDevelopment/Agents/RemoteFunctions.js b/src/LiveDevelopment/Agents/RemoteFunctions.js index e5119c1981e..cb11e072c26 100644 --- a/src/LiveDevelopment/Agents/RemoteFunctions.js +++ b/src/LiveDevelopment/Agents/RemoteFunctions.js @@ -30,10 +30,29 @@ * modules should define a single function that returns an object of all * exported functions. */ -function RemoteFunctions(experimental, remoteWSPort) { +function RemoteFunctions(config, remoteWSPort) { "use strict"; + var experimental; + if (!config) { + experimental = false; + } else { + experimental = config.experimental; + } var lastKeepAliveTime = Date.now(); + var req, timeout; + var animateHighlight = function (time) { + if(req) { + window.cancelAnimationFrame(req); + window.clearTimeout(timeout); + } + req = window.requestAnimationFrame(redrawHighlights); + + timeout = setTimeout(function () { + window.cancelAnimationFrame(req); + req = null; + }, time * 1000); + }; /** * @type {DOMEditHandler} @@ -250,56 +269,209 @@ function RemoteFunctions(experimental, remoteWSPort) { } return false; }, - _makeHighlightDiv: function (element, doAnimation) { var elementBounds = element.getBoundingClientRect(), highlight = window.document.createElement("div"), - styles = window.getComputedStyle(element); + elementStyling = window.getComputedStyle(element), + transitionDuration = parseFloat(elementStyling.getPropertyValue('transition-duration')), + animationDuration = parseFloat(elementStyling.getPropertyValue('animation-duration')); + + if (transitionDuration) { + animateHighlight(transitionDuration); + } + + if (animationDuration) { + animateHighlight(animationDuration); + } // Don't highlight elements with 0 width & height if (elementBounds.width === 0 && elementBounds.height === 0) { return; } + + var realElBorder = { + right: elementStyling.getPropertyValue('border-right-width'), + left: elementStyling.getPropertyValue('border-left-width'), + top: elementStyling.getPropertyValue('border-top-width'), + bottom: elementStyling.getPropertyValue('border-bottom-width') + }; + + var borderBox = elementStyling.boxSizing === 'border-box'; + + var innerWidth = parseFloat(elementStyling.width), + innerHeight = parseFloat(elementStyling.height), + outerHeight = innerHeight, + outerWidth = innerWidth; + + if (!borderBox) { + innerWidth += parseFloat(elementStyling.paddingLeft) + parseFloat(elementStyling.paddingRight); + innerHeight += parseFloat(elementStyling.paddingTop) + parseFloat(elementStyling.paddingBottom); + outerWidth = innerWidth + parseFloat(realElBorder.right) + + parseFloat(realElBorder.left), + outerHeight = innerHeight + parseFloat(realElBorder.bottom) + parseFloat(realElBorder.top); + } + + + var visualisations = { + horizontal: "left, right", + vertical: "top, bottom" + }; + + var drawPaddingRect = function(side) { + var elStyling = {}; + + if (visualisations.horizontal.indexOf(side) >= 0) { + elStyling['width'] = elementStyling.getPropertyValue('padding-' + side); + elStyling['height'] = innerHeight + "px"; + elStyling['top'] = 0; + + if (borderBox) { + elStyling['height'] = innerHeight - parseFloat(realElBorder.top) - parseFloat(realElBorder.bottom) + "px"; + } + + } else { + elStyling['height'] = elementStyling.getPropertyValue('padding-' + side); + elStyling['width'] = innerWidth + "px"; + elStyling['left'] = 0; + + if (borderBox) { + elStyling['width'] = innerWidth - parseFloat(realElBorder.left) - parseFloat(realElBorder.right) + "px"; + } + } + + elStyling[side] = 0; + elStyling['position'] = 'absolute'; + + return elStyling; + }; + + var drawMarginRect = function(side) { + var elStyling = {}; + + var margin = []; + margin['right'] = parseFloat(elementStyling.getPropertyValue('margin-right')); + margin['top'] = parseFloat(elementStyling.getPropertyValue('margin-top')); + margin['bottom'] = parseFloat(elementStyling.getPropertyValue('margin-bottom')); + margin['left'] = parseFloat(elementStyling.getPropertyValue('margin-left')); + + if(visualisations['horizontal'].indexOf(side) >= 0) { + + elStyling['width'] = elementStyling.getPropertyValue('margin-' + side); + elStyling['height'] = outerHeight + margin['top'] + margin['bottom'] + "px"; + elStyling['top'] = "-" + (margin['top'] + parseFloat(realElBorder.top)) + "px"; + } else { + elStyling['height'] = elementStyling.getPropertyValue('margin-' + side); + elStyling['width'] = outerWidth + "px"; + elStyling['left'] = "-" + realElBorder.left; + } + elStyling[side] = "-" + (margin[side] + parseFloat(realElBorder[side])) + "px"; + elStyling['position'] = 'absolute'; + + return elStyling; + }; + + var setVisibility = function (el) { + if ( + !config.remoteHighlight.showPaddingMargin || + parseInt(el.height, 10) <= 0 || + parseInt(el.width, 10) <= 0 + ) { + el.display = 'none'; + } else { + el.display = 'block'; + } + }; + + var mainBoxStyles = config.remoteHighlight.stylesToSet; + + var paddingVisualisations = [ + drawPaddingRect('top'), + drawPaddingRect('right'), + drawPaddingRect('bottom'), + drawPaddingRect('left') + ]; + + var marginVisualisations = [ + drawMarginRect('top'), + drawMarginRect('right'), + drawMarginRect('bottom'), + drawMarginRect('left') + ]; + + var setupVisualisations = function (arr, config) { + var i; + for (i = 0; i < arr.length; i++) { + setVisibility(arr[i]); + + // Applies to every visualisationElement (padding or margin div) + arr[i]["transform"] = "none"; + var el = window.document.createElement("div"), + styles = Object.assign( + {}, + config, + arr[i] + ); + + _setStyleValues(styles, el.style); + + highlight.appendChild(el); + } + }; + + setupVisualisations( + marginVisualisations, + config.remoteHighlight.marginStyling + ); + setupVisualisations( + paddingVisualisations, + config.remoteHighlight.paddingStyling + ); + highlight.className = HIGHLIGHT_CLASSNAME; var offset = _screenOffset(element); + + var el = element, + offsetLeft = 0, + offsetTop = 0; + + // Probably the easiest way to get elements position without including transform + do { + offsetLeft += el.offsetLeft; + offsetTop += el.offsetTop; + el = el.offsetParent; + } while(el); var stylesToSet = { - "left": offset.left + "px", - "top": offset.top + "px", - "width": elementBounds.width + "px", - "height": elementBounds.height + "px", + "left": offsetLeft + "px", + "top": offsetTop + "px", + "width": innerWidth + "px", + "height": innerHeight + "px", "z-index": 2000000, "margin": 0, "padding": 0, "position": "absolute", "pointer-events": "none", - "border-top-left-radius": styles.borderTopLeftRadius, - "border-top-right-radius": styles.borderTopRightRadius, - "border-bottom-left-radius": styles.borderBottomLeftRadius, - "border-bottom-right-radius": styles.borderBottomRightRadius, - "border-style": "solid", - "border-width": "1px", - "border-color": "#00a2ff", "box-shadow": "0 0 1px #fff", - "box-sizing": "border-box" + "box-sizing": elementStyling.getPropertyValue('box-sizing'), + "border-right": elementStyling.getPropertyValue('border-right'), + "border-left": elementStyling.getPropertyValue('border-left'), + "border-top": elementStyling.getPropertyValue('border-top'), + "border-bottom": elementStyling.getPropertyValue('border-bottom'), + "transform": elementStyling.getPropertyValue('transform'), + "transform-origin": elementStyling.getPropertyValue('transform-origin'), + "border-color": config.remoteHighlight.borderColor }; + + var mergedStyles = Object.assign({}, stylesToSet, config.remoteHighlight.stylesToSet); - var animateStartValues = { - "background-color": "rgba(0, 162, 255, 0.5)", - "opacity": 0 - }; + var animateStartValues = config.remoteHighlight.animateStartValue; - var animateEndValues = { - "background-color": "rgba(0, 162, 255, 0)", - "opacity": 1 - }; + var animateEndValues = config.remoteHighlight.animateEndValue; var transitionValues = { - "-webkit-transition-property": "opacity, background-color", - "-webkit-transition-duration": "300ms, 2.3s", - "transition-property": "opacity, background-color", + "transition-property": "opacity, background-color, transform", "transition-duration": "300ms, 2.3s" }; @@ -311,7 +483,7 @@ function RemoteFunctions(experimental, remoteWSPort) { } } - _setStyleValues(stylesToSet, highlight.style); + _setStyleValues(mergedStyles, highlight.style); _setStyleValues( doAnimation ? animateStartValues : animateEndValues, highlight.style @@ -842,6 +1014,11 @@ function RemoteFunctions(experimental, remoteWSPort) { function getSimpleDOM() { return JSON.stringify(_domElementToJSON(window.document.documentElement)); } + + function updateConfig(newConfig) { + config = JSON.parse(newConfig); + return JSON.stringify(config); + } // init _editHandler = new DOMEditHandler(window.document); @@ -871,10 +1048,10 @@ function RemoteFunctions(experimental, remoteWSPort) { _ws.onopen = function () { window.document.addEventListener("click", onDocumentClick); }; - + _ws.onmessage = function (evt) { }; - + _ws.onclose = function () { // websocket is closed window.document.removeEventListener("click", onDocumentClick); @@ -894,6 +1071,7 @@ function RemoteFunctions(experimental, remoteWSPort) { "highlightRule" : highlightRule, "redrawHighlights" : redrawHighlights, "applyDOMEdits" : applyDOMEdits, - "getSimpleDOM" : getSimpleDOM + "getSimpleDOM" : getSimpleDOM, + "updateConfig" : updateConfig }; } diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index e7aabc6cdf1..eab5252a7f2 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -79,6 +79,31 @@ define(function main(require, exports, module) { description: Strings.DESCRIPTION_LIVE_DEV_MULTIBROWSER }); + // "livedev.remoteHighlight" preference + var PREF_REMOTEHIGHLIGHT = "remoteHighlight"; + var remoteHighlightPref = prefs.definePreference(PREF_REMOTEHIGHLIGHT, "object", { + animateStartValue: { + "background-color": "rgba(0, 162, 255, 0.5)", + "opacity": 0 + }, + animateEndValue: { + "background-color": "rgba(0, 162, 255, 0)", + "opacity": 0.6 + }, + "paddingStyling": { + "border-width": "1px", + "border-style": "dashed", + "border-color": "rgba(0, 162, 255, 0.5)" + }, + "marginStyling": { + "background-color": "rgba(21, 165, 255, 0.58)" + }, + "borderColor": "rgba(21, 165, 255, 0.85)", + "showPaddingMargin": true + }, { + description: Strings.DESCRIPTION_LIVE_DEV_HIGHLIGHT_SETTINGS + }); + /** Toggles or sets the preference **/ function _togglePref(key, value) { var val, @@ -302,6 +327,7 @@ define(function main(require, exports, module) { /** Initialize LiveDevelopment */ AppInit.appReady(function () { params.parse(); + config.remoteHighlight = prefs.get(PREF_REMOTEHIGHLIGHT); Inspector.init(config); LiveDevelopment.init(config); @@ -353,6 +379,15 @@ define(function main(require, exports, module) { _setImplementation(prefs.get(PREF_MULTIBROWSER)); } }); + + remoteHighlightPref + .on("change", function () { + config.remoteHighlight = prefs.get(PREF_REMOTEHIGHLIGHT); + + if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) { + LiveDevImpl.agents.remote.call("updateConfig",JSON.stringify(config)); + } + }); }); diff --git a/src/config.json b/src/config.json index 55a2c5db84c..54615dc75af 100644 --- a/src/config.json +++ b/src/config.json @@ -23,8 +23,8 @@ "healthDataServerURL": "https://healthdev.brackets.io/healthDataLog" }, "name": "Brackets", - "version": "1.9.0-0", - "apiVersion": "1.9.0", + "version": "1.10.0-0", + "apiVersion": "1.10.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" @@ -58,8 +58,8 @@ "grunt-cli": "0.1.9", "phantomjs": "1.9.18", "grunt-lib-phantomjs": "0.3.0", - "grunt-eslint": "18.1.0", - "grunt-contrib-watch": "0.4.3", + "grunt-eslint": "19.0.0", + "grunt-contrib-watch": "1.0.0", "grunt-contrib-jasmine": "0.4.2", "grunt-template-jasmine-requirejs": "0.1.0", "grunt-contrib-cssmin": "0.6.0", diff --git a/src/document/Document.js b/src/document/Document.js index a2ae75f52dc..fce97e4cf1a 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -758,6 +758,25 @@ define(function (require, exports, module) { return this.file instanceof InMemoryFile; }; + /** + * Reloads the document from FileSystem + * @return {promise} - to check if reload was successful or not + */ + Document.prototype.reload = function () { + var $deferred = $.Deferred(); + var self = this; + FileUtils.readAsText(this.file) + .done(function (text, readTimestamp) { + self.refreshText(text, readTimestamp); + $deferred.resolve(); + }) + .fail(function (error) { + console.log("Error reloading contents of " + self.file.fullPath, error); + $deferred.reject(); + }); + return $deferred.promise(); + }; + // We dispatch events from the module level, and the instance level. Instance events are wired up // in the Document constructor. EventDispatcher.makeEventDispatcher(exports); diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index a0c2794a1be..92cc93a3726 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -322,6 +322,22 @@ define(function (require, exports, module) { }); var file = FileSystem.getFileForPath(fullPath); + if (options && options.encoding) { + file._encoding = options.encoding; + } else { + var projectRoot = ProjectManager.getProjectRoot(), + context = { + location : { + scope: "user", + layer: "project", + layerID: projectRoot.fullPath + } + }; + var encoding = PreferencesManager.getViewState("encoding", context); + if (encoding[fullPath]) { + file._encoding = encoding[fullPath]; + } + } MainViewManager._open(paneId, file, options) .done(function () { result.resolve(file); @@ -626,10 +642,8 @@ define(function (require, exports, module) { // If a file is currently selected in the tree, put it next to it. // If a directory is currently selected in the tree, put it in it. // If an Untitled document is selected or nothing is selected in the tree, put it at the root of the project. - // (Note: 'selected' may be an item that's selected in the workingset and not the tree; but in that case - // ProjectManager.createNewItem() ignores the baseDir we give it and falls back to the project root on its own) var baseDirEntry, - selected = ProjectManager.getSelectedItem(); + selected = ProjectManager.getFileTreeContext(); if ((!selected) || (selected instanceof InMemoryFile)) { selected = ProjectManager.getProjectRoot(); } @@ -902,7 +916,21 @@ define(function (require, exports, module) { doc.isSaving = true; // mark that we're saving the document // First, write document's current text to new file + if (doc.file._encoding && doc.file._encoding !== "UTF-8") { + var projectRoot = ProjectManager.getProjectRoot(), + context = { + location : { + scope: "user", + layer: "project", + layerID: projectRoot.fullPath + } + }; + var encoding = PreferencesManager.getViewState("encoding", context); + encoding[path] = doc.file._encoding; + PreferencesManager.setViewState("encoding", encoding, context); + } newFile = FileSystem.getFileForPath(path); + newFile._encoding = doc.file._encoding; // Save as warns you when you're about to overwrite a file, so we // explicitly allow "blind" writes to the filesystem in this case, diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index b6b1bead62d..1f6ce37a54a 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -93,6 +93,7 @@ define(function (require, exports, module) { Commands = require("command/Commands"), PerfUtils = require("utils/PerfUtils"), LanguageManager = require("language/LanguageManager"), + ProjectManager = require("project/ProjectManager"), Strings = require("strings"); @@ -419,7 +420,7 @@ define(function (require, exports, module) { if (doc) { result.resolve(doc.getText(), doc.diskTimestamp, checkLineEndings ? doc._lineEndings : null); } else { - file.read(function (err, contents, stat) { + file.read(function (err, contents, encoding, stat) { if (err) { result.reject(err); } else { @@ -498,6 +499,18 @@ define(function (require, exports, module) { // via notifyFileDeleted FileSyncManager.syncOpenDocuments(Strings.FILE_DELETED_TITLE); + var projectRoot = ProjectManager.getProjectRoot(), + context = { + location : { + scope: "user", + layer: "project", + layerID: projectRoot.fullPath + } + }; + var encoding = PreferencesManager.getViewState("encoding", context); + delete encoding[fullPath]; + PreferencesManager.setViewState("encoding", encoding, context); + if (!getOpenDocumentForPath(fullPath) && !MainViewManager.findInAllWorkingSets(fullPath).length) { // For images not open in the workingset, diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 0596833d3dc..2060fc4d838 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -75,11 +75,13 @@ define(function (require, exports, module) { TextRange = require("document/TextRange").TextRange, TokenUtils = require("utils/TokenUtils"), ValidationUtils = require("utils/ValidationUtils"), + HTMLUtils = require("language/HTMLUtils"), ViewUtils = require("utils/ViewUtils"), MainViewManager = require("view/MainViewManager"), _ = require("thirdparty/lodash"); /** Editor preferences */ + var CLOSE_BRACKETS = "closeBrackets", CLOSE_TAGS = "closeTags", DRAG_DROP = "dragDropText", @@ -96,7 +98,9 @@ define(function (require, exports, module) { UPPERCASE_COLORS = "uppercaseColors", USE_TAB_CHAR = "useTabChar", WORD_WRAP = "wordWrap", - INDENT_LINE_COMMENT = "indentLineComment"; + INDENT_LINE_COMMENT = "indentLineComment", + INPUT_STYLE = "inputStyle"; + /** * A list of gutter name and priorities currently registered for editors. @@ -137,6 +141,7 @@ define(function (require, exports, module) { cmOptions[TAB_SIZE] = "tabSize"; cmOptions[USE_TAB_CHAR] = "indentWithTabs"; cmOptions[WORD_WRAP] = "lineWrapping"; + cmOptions[INPUT_STYLE] = "inputStyle"; PreferencesManager.definePreference(CLOSE_BRACKETS, "boolean", true, { description: Strings.DESCRIPTION_CLOSE_BRACKETS @@ -223,10 +228,12 @@ define(function (require, exports, module) { PreferencesManager.definePreference(WORD_WRAP, "boolean", true, { description: Strings.DESCRIPTION_WORD_WRAP }); - PreferencesManager.definePreference(INDENT_LINE_COMMENT, "boolean", false, { description: Strings.DESCRIPTION_INDENT_LINE_COMMENT }); + PreferencesManager.definePreference(INPUT_STYLE, "string", "textarea", { + description: Strings.DESCRIPTION_INPUT_STYLE + }); var editorOptions = Object.keys(cmOptions); @@ -413,7 +420,7 @@ define(function (require, exports, module) { highlightSelectionMatches : currentOptions[HIGHLIGHT_MATCHES], indentUnit : currentOptions[USE_TAB_CHAR] ? currentOptions[TAB_SIZE] : currentOptions[SPACE_UNITS], indentWithTabs : currentOptions[USE_TAB_CHAR], - inputStyle : "textarea", // the "contenteditable" mode used on mobiles could cause issues + inputStyle : currentOptions[INPUT_STYLE], lineNumbers : currentOptions[SHOW_LINE_NUMBERS], lineWiseCopyCut : currentOptions[LINEWISE_COPY_CUT], lineWrapping : currentOptions[WORD_WRAP], @@ -1010,6 +1017,9 @@ define(function (require, exports, module) { this._codeMirror.on("cursorActivity", function (instance) { self.trigger("cursorActivity", self); }); + this._codeMirror.on("beforeSelectionChange", function (instance, selectionObj) { + self.trigger("beforeSelectionChange", selectionObj); + }); this._codeMirror.on("scroll", function (instance) { // If this editor is visible, close all dropdowns on scroll. // (We don't want to do this if we're just scrolling in a non-visible editor @@ -2203,6 +2213,19 @@ define(function (require, exports, module) { isMixed = (outerMode.name !== startMode.name); if (isMixed) { + // This is the magic code to let the code view know that we are in 'css' context + // if the CodeMirror outermode is 'htmlmixed' and we are in 'style' attributes + // value context. This has to be done as CodeMirror doesn't yet think this as 'css' + // This magic is executed only when user is having a cursor and not selection + // We will enable selection handling one we figure a way out to handle mixed scope selection + if (outerMode.name === 'htmlmixed' && primarySel.start.line === primarySel.end.line && primarySel.start.ch === primarySel.end.ch) { + var tagInfo = HTMLUtils.getTagInfo(this, primarySel.start, true), + tokenType = tagInfo.position.tokenType; + + if (tokenType === HTMLUtils.ATTR_VALUE && tagInfo.attr.name.toLowerCase() === 'style') { + return 'css'; + } + } // Shortcut the first check to avoid getModeAt(), which can be expensive if (primarySel.start.line !== primarySel.end.line || primarySel.start.ch !== primarySel.end.ch) { var endMode = TokenUtils.getModeAt(this._codeMirror, primarySel.end); @@ -2682,8 +2705,8 @@ define(function (require, exports, module) { }; /** - * Sets lineCommentIndent option. - * + * Sets indentLineComment option. + * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid @@ -2694,7 +2717,7 @@ define(function (require, exports, module) { }; /** - * Returns true if word wrap is enabled for the specified or current file + * Returns true if indentLineComment is enabled for the specified or current file * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 3a0dcf7c457..69307e250f7 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -297,6 +297,22 @@ define(function (require, exports, module) { return false; } + /** + * Return the column of the first non whitespace char in the given line. + * + * @private + * @param {!Document} doc + * @param {number} lineNum + * @returns {number} the column index or null + */ + function _firstNotWs(doc, lineNum) { + var text = doc.getLine(lineNum); + if (text === null || text === undefined) { + return 0; + } + + return text.search(/\S|$/); + } /** * Generates an edit that adds or removes block-comment tokens to the selection, preserving selection @@ -320,12 +336,13 @@ define(function (require, exports, module) { * The selection to block comment/uncomment. * @param {?Array.<{!{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}}>} selectionsToTrack * An array of selections that should be tracked through this edit. + * @param {String} command The command callee. It cans be "line" or "block". * @return {{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}} * An edit description suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ - function _getBlockCommentPrefixSuffixEdit(editor, prefix, suffix, linePrefixes, sel, selectionsToTrack) { + function _getBlockCommentPrefixSuffixEdit(editor, prefix, suffix, linePrefixes, sel, selectionsToTrack, command) { var doc = editor.document, ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}), selEndIndex = editor.indexFromPos(sel.end), @@ -345,6 +362,12 @@ define(function (require, exports, module) { var searchCtx, atSuffix, suffixEnd, initialPos, endLine; + var indentLineComment = Editor.getIndentLineComment(); + + function isIndentLineCommand() { + return indentLineComment && command === "line"; + } + if (!selectionsToTrack) { // Track the original selection. selectionsToTrack = [_.cloneDeep(sel)]; @@ -467,12 +490,31 @@ define(function (require, exports, module) { // Comment out - add the suffix to the start and the prefix to the end. if (canComment) { var completeLineSel = sel.start.ch === 0 && sel.end.ch === 0 && sel.start.line < sel.end.line; + var startCh = _firstNotWs(doc, sel.start.line); if (completeLineSel) { - editGroup.push({text: suffix + "\n", start: sel.end}); - editGroup.push({text: prefix + "\n", start: sel.start}); + if (isIndentLineCommand()) { + var endCh = _firstNotWs(doc, sel.end.line - 1); + var useTabChar = Editor.getUseTabChar(editor.document.file.fullPath); + var indentChar = useTabChar ? "\t" : " "; + editGroup.push({ + text: _.repeat(indentChar, endCh) + suffix + "\n", + start: {line: sel.end.line, ch: 0} + }); + editGroup.push({ + text: prefix + "\n" + _.repeat(indentChar, startCh), + start: {line: sel.start.line, ch: startCh} + }); + } else { + editGroup.push({text: suffix + "\n", start: sel.end}); + editGroup.push({text: prefix + "\n", start: sel.start}); + } } else { editGroup.push({text: suffix, start: sel.end}); - editGroup.push({text: prefix, start: sel.start}); + if (isIndentLineCommand()) { + editGroup.push({text: prefix, start: { line: sel.start.line, ch: startCh }}); + } else { + editGroup.push({text: prefix, start: sel.start}); + } } // Correct the tracked selections. We can't just use the default selection fixup, @@ -497,7 +539,7 @@ define(function (require, exports, module) { if (completeLineSel) { // Just move the line down. pos.line++; - } else if (pos.line === sel.start.line) { + } else if (pos.line === sel.start.line && !(isIndentLineCommand() && pos.ch < startCh)) { pos.ch += prefix.length; } } @@ -513,16 +555,21 @@ define(function (require, exports, module) { // If both are found we assume that a complete line selection comment added new lines, so we remove them. var line = doc.getLine(prefixPos.line).trim(), prefixAtStart = prefixPos.ch === 0 && prefix.length === line.length, - suffixAtStart = false; + prefixIndented = indentLineComment && prefix.length === line.length, + suffixAtStart = false, + suffixIndented = false; if (suffixPos) { line = doc.getLine(suffixPos.line).trim(); suffixAtStart = suffixPos.ch === 0 && suffix.length === line.length; + suffixIndented = indentLineComment && suffix.length === line.length; } // Remove the suffix if there is one if (suffixPos) { - if (prefixAtStart && suffixAtStart) { + if (suffixIndented) { + editGroup.push({text: "", start: {line: suffixPos.line, ch: 0}, end: {line: suffixPos.line + 1, ch: 0}}); + } else if (prefixAtStart && suffixAtStart) { editGroup.push({text: "", start: suffixPos, end: {line: suffixPos.line + 1, ch: 0}}); } else { editGroup.push({text: "", start: suffixPos, end: {line: suffixPos.line, ch: suffixPos.ch + suffix.length}}); @@ -530,7 +577,9 @@ define(function (require, exports, module) { } // Remove the prefix - if (prefixAtStart && suffixAtStart) { + if (prefixIndented) { + editGroup.push({text: "", start: {line: prefixPos.line, ch: 0}, end: {line: prefixPos.line + 1, ch: 0}}); + } else if (prefixAtStart && suffixAtStart) { editGroup.push({text: "", start: prefixPos, end: {line: prefixPos.line + 1, ch: 0}}); } else { editGroup.push({text: "", start: prefixPos, end: {line: prefixPos.line, ch: prefixPos.ch + prefix.length}}); @@ -566,12 +615,13 @@ define(function (require, exports, module) { * lineSel A line selection as returned from `Editor.convertToLineSelections()`. `selectionForEdit` is the selection to perform * the line comment operation on, and `selectionsToTrack` are a set of selections associated with this line that need to be * tracked through the edit. + * @param {String} command The command callee. It cans be "line" or "block". * @return {{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}} * An edit description suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ - function _getLineCommentPrefixSuffixEdit(editor, prefix, suffix, lineSel) { + function _getLineCommentPrefixSuffixEdit(editor, prefix, suffix, lineSel, command) { var sel = lineSel.selectionForEdit; // For one-line selections, we shrink the selection to exclude the trailing newline. @@ -581,7 +631,7 @@ define(function (require, exports, module) { // Now just run the standard block comment code, but make sure to track any associated selections // that were subsumed into this line selection. - return _getBlockCommentPrefixSuffixEdit(editor, prefix, suffix, [], sel, lineSel.selectionsToTrack); + return _getBlockCommentPrefixSuffixEdit(editor, prefix, suffix, [], sel, lineSel.selectionsToTrack, command); } /** @@ -591,12 +641,13 @@ define(function (require, exports, module) { * @param {!Editor} editor The editor to edit within. * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>} * selections The selections we want to line-comment. + * @param {String} command The command callee. It cans be "line" or "block". * @return {Array.<{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}>} * An array of edit descriptions suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ - function _getLineCommentEdits(editor, selections) { + function _getLineCommentEdits(editor, selections, command) { // We need to expand line selections in order to coalesce cursors on the same line, but we // don't want to merge adjacent line selections. var lineSelections = editor.convertToLineSelections(selections, { mergeAdjacent: false }), @@ -611,7 +662,7 @@ define(function (require, exports, module) { if (language.hasLineCommentSyntax()) { edit = _getLineCommentPrefixEdit(editor, language.getLineCommentPrefixes(), language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), lineSel); } else if (language.hasBlockCommentSyntax()) { - edit = _getLineCommentPrefixSuffixEdit(editor, language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), lineSel); + edit = _getLineCommentPrefixSuffixEdit(editor, language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), lineSel, command); } } if (!edit) { @@ -633,7 +684,7 @@ define(function (require, exports, module) { return; } - editor.setSelections(editor.document.doMultipleEdits(_getLineCommentEdits(editor, editor.getSelections()))); + editor.setSelections(editor.document.doMultipleEdits(_getLineCommentEdits(editor, editor.getSelections(), "line"))); } /** @@ -674,7 +725,7 @@ define(function (require, exports, module) { // Handle any line-comment edits. It's okay if these are out-of-order with the other edits, since // they shouldn't overlap, and `doMultipleEdits()` will take care of sorting the edits so the // selections can be tracked appropriately. - edits.push.apply(edits, _getLineCommentEdits(editor, lineCommentSels)); + edits.push.apply(edits, _getLineCommentEdits(editor, lineCommentSels, "block")); editor.setSelections(editor.document.doMultipleEdits(edits)); } diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 593a91e05c9..7d8531a5886 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -40,10 +40,24 @@ define(function (require, exports, module) { PreferencesManager = require("preferences/PreferencesManager"), StatusBar = require("widgets/StatusBar"), Strings = require("strings"), + FileUtils = require("file/FileUtils"), + InMemoryFile = require("document/InMemoryFile"), + Dialogs = require("widgets/Dialogs"), + DefaultDialogs = require("widgets/DefaultDialogs"), + ProjectManager = require("project/ProjectManager"), + Async = require("utils/Async"), + FileSystem = require("filesystem/FileSystem"), + CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + DocumentManager = require("document/DocumentManager"), StringUtils = require("utils/StringUtils"); + + var SupportedEncodingsText = require("text!supported-encodings.json"), + SupportedEncodings = JSON.parse(SupportedEncodingsText); /* StatusBar indicators */ var languageSelect, // this is a DropdownButton instance + encodingSelect, // this is a DropdownButton instance $cursorInfo, $fileInfo, $indentType, @@ -74,12 +88,24 @@ define(function (require, exports, module) { var doc = editor.document, lang = doc.getLanguage(); - // Ensure width isn't left locked by a previous click of the dropdown (which may not have resulted in a "change" event at the time) - languageSelect.$button.css("width", "auto"); // Show the current language as button title languageSelect.$button.text(lang.getName()); } + /** + * Update encoding + * @param {Editor} editor Current editor + */ + function _updateEncodingInfo(editor) { + var doc = editor.document; + + // Show the current encoding as button title + if (!doc.file._encoding) { + doc.file._encoding = "UTF-8"; + } + encodingSelect.$button.text(doc.file._encoding); + } + /** * Update file information * @param {Editor} editor Current editor @@ -277,6 +303,7 @@ define(function (require, exports, module) { _updateCursorInfo(null, current); _updateLanguageInfo(current); + _updateEncodingInfo(current); _updateFileInfo(current); _initOverwriteMode(current); _updateIndentType(fullPath); @@ -305,6 +332,40 @@ define(function (require, exports, module) { languageSelect.items.unshift(LANGUAGE_SET_AS_DEFAULT); } + /** + * Change the encoding and reload the current document. + * If passed then save the preferred encoding in state. + */ + function _changeEncodingAndReloadDoc(document) { + var promise = document.reload(); + promise.done(function (text, readTimestamp) { + encodingSelect.$button.text(document.file._encoding); + // Store the preferred encoding in the state + var projectRoot = ProjectManager.getProjectRoot(), + context = { + location : { + scope: "user", + layer: "project", + layerID: projectRoot.fullPath + } + }; + var encoding = PreferencesManager.getViewState("encoding", context); + encoding[document.file.fullPath] = document.file._encoding; + PreferencesManager.setViewState("encoding", encoding, context); + }); + promise.fail(function (error) { + console.log("Error reloading contents of " + document.file.fullPath, error); + }); + } + + + /** + * Populate the encodingSelect DropdownButton's menu with all registered encodings + */ + function _populateEncodingDropdown() { + encodingSelect.items = SupportedEncodings; + } + /** * Initialize */ @@ -343,6 +404,27 @@ define(function (require, exports, module) { $("#status-language").append(languageSelect.$button); languageSelect.$button.attr("title", Strings.STATUSBAR_LANG_TOOLTIP); + + encodingSelect = new DropdownButton("", [], function (item, index) { + var document = EditorManager.getActiveEditor().document; + var html = _.escape(item); + + // Show indicators for currently selected & default languages for the current file + if (item === "UTF-8") { + html += " " + Strings.STATUSBAR_DEFAULT_LANG + ""; + } + if (item === document.file._encoding) { + html = "" + html; + } + return html; + }); + + encodingSelect.dropdownExtraClasses = "dropdown-status-bar"; + encodingSelect.$button.addClass("btn-status-bar"); + $("#status-encoding").append(encodingSelect.$button); + encodingSelect.$button.attr("title", Strings.STATUSBAR_ENCODING_TOOLTIP); + + // indentation event handlers $indentType.on("click", _toggleIndentType); $indentWidthLabel @@ -389,16 +471,77 @@ define(function (require, exports, module) { } }); + // Encoding select change handler + encodingSelect.on("select", function (e, encoding) { + var document = EditorManager.getActiveEditor().document, + originalPath = document.file.fullPath, + originalEncoding = document.file._encoding; + + document.file._encoding = encoding; + if (!(document.file instanceof InMemoryFile) && document.isDirty) { + CommandManager.execute(Commands.FILE_SAVE_AS, {doc: document}).done(function () { + var doc = DocumentManager.getCurrentDocument(); + if (originalPath === doc.file.fullPath) { + _changeEncodingAndReloadDoc(doc); + } else { + document.file._encoding = originalEncoding; + } + }); + } else if (document.file instanceof InMemoryFile) { + encodingSelect.$button.text(encoding); + } else if (!document.isDirty) { + _changeEncodingAndReloadDoc(document); + } + }); + $statusOverwrite.on("click", _updateEditorOverwriteMode); } // Initialize: status bar focused listener EditorManager.on("activeEditorChange", _onActiveEditorChange); + function _checkFileExistance(filePath, index, encoding) { + var deferred = new $.Deferred(), + fileEntry = FileSystem.getFileForPath(filePath); + + fileEntry.exists(function (err, exists) { + if (!err && exists) { + deferred.resolve(); + } else { + delete encoding[filePath]; + deferred.reject(); + } + }); + + return deferred.promise(); + } + + ProjectManager.on("projectOpen", function () { + var projectRoot = ProjectManager.getProjectRoot(), + context = { + location : { + scope: "user", + layer: "project", + layerID: projectRoot.fullPath + } + }; + var encoding = PreferencesManager.getViewState("encoding", context); + if (!encoding) { + PreferencesManager.setViewState("encoding", {}, context); + } + Async.doSequentially(Object.keys(encoding), function (filePath, index) { + return _checkFileExistance(filePath, index, encoding); + }, false) + .always(function () { + PreferencesManager.setViewState("encoding", encoding, context); + }); + }); + AppInit.htmlReady(_init); AppInit.appReady(function () { // Populate language switcher with all languages after startup; update it later if this set changes _populateLanguageDropdown(); + _populateEncodingDropdown(); LanguageManager.on("languageAdded languageModified", _populateLanguageDropdown); _onActiveEditorChange(null, EditorManager.getActiveEditor(), null); StatusBar.show(); diff --git a/src/extensibility/ExtensionManagerDialog.js b/src/extensibility/ExtensionManagerDialog.js index 64d4b712ec0..308810900b4 100644 --- a/src/extensibility/ExtensionManagerDialog.js +++ b/src/extensibility/ExtensionManagerDialog.js @@ -321,6 +321,7 @@ define(function (require, exports, module) { } models.push(new ExtensionManagerViewModel.InstalledViewModel()); + models.push(new ExtensionManagerViewModel.DefaultViewModel()); function updateSearchDisabled() { var model = models[_activeTabIndex], diff --git a/src/extensibility/ExtensionManagerView.js b/src/extensibility/ExtensionManagerView.js index 2a981ff6c68..e6b25659b34 100644 --- a/src/extensibility/ExtensionManagerView.js +++ b/src/extensibility/ExtensionManagerView.js @@ -336,10 +336,10 @@ define(function (require, exports, module) { context.removalAllowed = this.model.source === "installed" && !context.failedToStart && !hasPendingAction; - context.disablingAllowed = this.model.source === "installed" && - !context.disabled && !hasPendingAction; - context.enablingAllowed = this.model.source === "installed" && - context.disabled && !hasPendingAction; + var isDefaultOrInstalled = this.model.source === "default" || this.model.source === "installed"; + var isDefaultAndTheme = this.model.source === "default" && context.metadata.theme; + context.disablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && !context.disabled && !hasPendingAction; + context.enablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && context.disabled && !hasPendingAction; // Copy over helper functions that we share with the registry app. ["lastVersionDate", "authorInfo"].forEach(function (helper) { diff --git a/src/extensibility/ExtensionManagerViewModel.js b/src/extensibility/ExtensionManagerViewModel.js index cb86d1327ee..73577e8fa90 100644 --- a/src/extensibility/ExtensionManagerViewModel.js +++ b/src/extensibility/ExtensionManagerViewModel.js @@ -80,6 +80,12 @@ define(function (require, exports, module) { */ ExtensionManagerViewModel.prototype.SOURCE_INSTALLED = "installed"; + /** + * @type {string} + * Constant indicating that this model/view should initialize from the list of default bundled extensions. + */ + ExtensionManagerViewModel.prototype.SOURCE_DEFAULT = "default"; + /** * @type {Object} * The current set of extensions managed by this model. Same as ExtensionManager.extensions. @@ -509,6 +515,65 @@ define(function (require, exports, module) { return entry; }; + /** + * Model for displaying default extensions that come bundled with Brackets + */ + function DefaultViewModel() { + ExtensionManagerViewModel.call(this); + } + + // Inheritance setup + DefaultViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); + DefaultViewModel.prototype.constructor = DefaultViewModel; + + /** + * Add SOURCE_DEFAULT to DefaultViewModel + */ + DefaultViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_DEFAULT; + + /** + * Initializes the model from the set of default extensions, sorted alphabetically by id + * @return {$.Promise} a promise that's resolved when we're done initializing. + */ + DefaultViewModel.prototype._initializeFromSource = function () { + var self = this; + this.extensions = ExtensionManager.extensions; + this.sortedFullSet = Object.keys(this.extensions) + .filter(function (key) { + return self.extensions[key].installInfo && + self.extensions[key].installInfo.locationType === ExtensionManager.LOCATION_DEFAULT; + }); + this._sortFullSet(); + this._setInitialFilter(); + return new $.Deferred().resolve().promise(); + }; + + /** + * @private + * Re-sorts the current full set + */ + DefaultViewModel.prototype._sortFullSet = function () { + var self = this; + this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) { + var metadata1 = self.extensions[key1].installInfo.metadata, + metadata2 = self.extensions[key2].installInfo.metadata, + id1 = (metadata1.title || metadata1.name).toLocaleLowerCase(), + id2 = (metadata2.title || metadata2.name).toLocaleLowerCase(); + return id1.localeCompare(id2); + }); + }; + + /** + * @private + * Finds the default extension metadata by id. If there is no default extension matching the given id, + * this returns `null`. + * @param {string} id of the theme extension + * @return {Object?} extension metadata or null if there's no matching extension + */ + DefaultViewModel.prototype._getEntry = function (id) { + return this.extensions[id] ? this.extensions[id].installInfo : null; + }; + /** * The model for the ExtensionManagerView that is responsible for handling registry-based theme extensions. * This extends ExtensionManagerViewModel. @@ -573,4 +638,5 @@ define(function (require, exports, module) { exports.RegistryViewModel = RegistryViewModel; exports.ThemesViewModel = ThemesViewModel; exports.InstalledViewModel = InstalledViewModel; + exports.DefaultViewModel = DefaultViewModel; }); \ No newline at end of file diff --git a/src/extensibility/Package.js b/src/extensibility/Package.js index 721ef3a2d05..d49647748a1 100644 --- a/src/extensibility/Package.js +++ b/src/extensibility/Package.js @@ -43,6 +43,11 @@ define(function (require, exports, module) { description: Strings.DESCRIPTION_PROXY }); + var PREF_EXTENSIONS_DEFAULT_DISABLED = "extensions.default.disabled"; + PreferencesManager.definePreference(PREF_EXTENSIONS_DEFAULT_DISABLED, "array", [], { + description: Strings.DESCRIPTION_DISABLED_DEFAULT_EXTENSIONS + }); + var Errors = { ERROR_LOADING: "ERROR_LOADING", MALFORMED_URL: "MALFORMED_URL", @@ -436,6 +441,23 @@ define(function (require, exports, module) { }); } + /** + * This function manages the PREF_EXTENSIONS_DEFAULT_DISABLED preference + * holding an array of default extension paths that should not be loaded + * on Brackets startup + */ + function toggleDefaultExtension(path, enabled) { + var arr = PreferencesManager.get(PREF_EXTENSIONS_DEFAULT_DISABLED); + if (!Array.isArray(arr)) { arr = []; } + var io = arr.indexOf(path); + if (enabled === true && io !== -1) { + arr.splice(io, 1); + } else if (enabled === false && io === -1) { + arr.push(path); + } + PreferencesManager.set(PREF_EXTENSIONS_DEFAULT_DISABLED, arr); + } + /** * Disables the extension at the given path. * @@ -446,12 +468,19 @@ define(function (require, exports, module) { function disable(path) { var result = new $.Deferred(), file = FileSystem.getFileForPath(path + "/.disabled"); + + var defaultExtensionPath = ExtensionLoader.getDefaultExtensionPath(); + if (file.fullPath.indexOf(defaultExtensionPath) === 0) { + toggleDefaultExtension(path, false); + result.resolve(); + return result.promise(); + } + file.write("", function (err) { if (err) { - result.reject(err); - } else { - result.resolve(); + return result.reject(err); } + result.resolve(); }); return result.promise(); } @@ -466,14 +495,25 @@ define(function (require, exports, module) { function enable(path) { var result = new $.Deferred(), file = FileSystem.getFileForPath(path + "/.disabled"); - file.unlink(function (err) { - if (err) { - result.reject(err); - return; - } + + function afterEnable() { ExtensionLoader.loadExtension(FileUtils.getBaseName(path), { baseUrl: path }, "main") .done(result.resolve) .fail(result.reject); + } + + var defaultExtensionPath = ExtensionLoader.getDefaultExtensionPath(); + if (file.fullPath.indexOf(defaultExtensionPath) === 0) { + toggleDefaultExtension(path, true); + afterEnable(); + return result.promise(); + } + + file.unlink(function (err) { + if (err) { + return result.reject(err); + } + afterEnable(); }); return result.promise(); } diff --git a/src/extensibility/node/npm-installer.js b/src/extensibility/node/npm-installer.js index a5271d87446..46d25cfbeb6 100644 --- a/src/extensibility/node/npm-installer.js +++ b/src/extensibility/node/npm-installer.js @@ -114,7 +114,7 @@ function performNpmInstallIfRequired(npmOptions, validationResult, callback) { packageJson = null; } - if (!packageJson || !packageJson.dependencies) { + if (!packageJson || !packageJson.dependencies || !Object.keys(packageJson.dependencies).length) { return finish(); } diff --git a/src/extensions/default/CSSAtRuleCodeHints/AtRulesDef.json b/src/extensions/default/CSSAtRuleCodeHints/AtRulesDef.json new file mode 100644 index 00000000000..671a926c897 --- /dev/null +++ b/src/extensions/default/CSSAtRuleCodeHints/AtRulesDef.json @@ -0,0 +1,10 @@ +{ + "@charset": "Defines the character set used by the style sheet.", + "@import": "Tells the CSS engine to include an external style sheet.", + "@namespace": "Tells the CSS engine that all its content must be considered prefixed with an XML namespace.", + "@media": "A conditional group rule which will apply its content if the device meets the criteria of the condition defined using a media query.", + "@supports": "A conditional group rule which will apply its content if the browser meets the criteria of the given condition.", + "@page": "Describes the aspect of layout changes which will be applied when printing the document.", + "@font-face": "Describes the aspect of an external font to be downloaded.", + "@keyframes": "Describes the aspect of intermediate steps in a CSS animation sequence." +} diff --git a/src/extensions/default/CSSAtRuleCodeHints/main.js b/src/extensions/default/CSSAtRuleCodeHints/main.js new file mode 100644 index 00000000000..a352a493a02 --- /dev/null +++ b/src/extensions/default/CSSAtRuleCodeHints/main.js @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +define(function (require, exports, module) { + "use strict"; + + // Load dependent modules + var AppInit = brackets.getModule("utils/AppInit"), + CodeHintManager = brackets.getModule("editor/CodeHintManager"), + AtRulesText = require("text!AtRulesDef.json"), + AtRules = JSON.parse(AtRulesText); + + + /** + * @constructor + */ + function AtRuleHints() { + } + + // As we are only going to provide @rules name hints + // we should claim that we don't have hints for anything else + AtRuleHints.prototype.hasHints = function (editor, implicitChar) { + var pos = editor.getCursorPos(), + token = editor._codeMirror.getTokenAt(pos), + cmState; + + this.editor = editor; + + if (token.state.base && token.state.base.localState) { + cmState = token.state.base.localState; + } else { + cmState = token.state; + } + + // Check if we are at '@' rule 'def' context + if ((token.type === "def" && cmState.context.type === "at") + || (token.type === "variable-2" && (cmState.context.type === "top" || cmState.context.type === "block"))) { + this.filter = token.string; + return true; + } else { + this.filter = null; + return false; + } + }; + + AtRuleHints.prototype.getHints = function (implicitChar) { + var pos = this.editor.getCursorPos(), + token = this.editor._codeMirror.getTokenAt(pos); + + this.filter = token.string; + this.token = token; + + if (!this.filter) { + return null; + } + + // Filter the property list based on the token string + var result = Object.keys(AtRules).filter(function (key) { + if (key.indexOf(token.string) === 0) { + return key; + } + }).sort(); + + return { + hints: result, + match: this.filter, + selectInitial: true, + defaultDescriptionWidth: true, + handleWideResults: false + }; + }; + + + /** + * Inserts a given @ hint into the current editor context. + * + * @param {string} completion + * The hint to be inserted into the editor context. + * + * @return {boolean} + * Indicates whether the manager should follow hint insertion with an + * additional explicit hint request. + */ + AtRuleHints.prototype.insertHint = function (completion) { + var cursor = this.editor.getCursorPos(); + this.editor.document.replaceRange(completion, {line: cursor.line, ch: this.token.start}, {line: cursor.line, ch: this.token.end}); + return false; + }; + + AppInit.appReady(function () { + // Register code hint providers + var restrictedBlockHints = new AtRuleHints(); + CodeHintManager.registerHintProvider(restrictedBlockHints, ["css", "less", "scss"], 0); + + // For unit testing + exports.restrictedBlockHints = restrictedBlockHints; + }); +}); diff --git a/src/extensions/default/CSSAtRuleCodeHints/unittests.js b/src/extensions/default/CSSAtRuleCodeHints/unittests.js new file mode 100644 index 00000000000..3044c706ce7 --- /dev/null +++ b/src/extensions/default/CSSAtRuleCodeHints/unittests.js @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global describe, it, xit, expect, beforeEach, afterEach */ + +define(function (require, exports, module) { + "use strict"; + + var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), + CSSAtRuleCodeHints = require("main"); + + describe("CSS '@' rules Code Hinting", function () { + + var defaultContent = "@ { \n" + + "} \n" + + " \n" + + "@m "; + + + var testDocument, testEditor; + + /* + * Create a mockup editor with the given content and language id. + * + * @param {string} content - content for test window + * @param {string} languageId + */ + function setupTest(content, languageId) { + var mock = SpecRunnerUtils.createMockEditor(content, languageId); + testDocument = mock.doc; + testEditor = mock.editor; + } + + function tearDownTest() { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + } + + // Ask provider for hints at current cursor position; expect it to return some + function expectHints(provider, implicitChar, returnWholeObj) { + expect(provider.hasHints(testEditor, implicitChar)).toBe(true); + var hintsObj = provider.getHints(); + expect(hintsObj).toBeTruthy(); + // return just the array of hints if returnWholeObj is falsy + return returnWholeObj ? hintsObj : hintsObj.hints; + } + + // Ask provider for hints at current cursor position; expect it NOT to return any + function expectNoHints(provider, implicitChar) { + expect(provider.hasHints(testEditor, implicitChar)).toBe(false); + } + + // compares lists to ensure they are the same + function verifyListsAreIdentical(hintList, values) { + var i; + expect(hintList.length).toBe(values.length); + for (i = 0; i < values.length; i++) { + expect(hintList[i]).toBe(values[i]); + } + } + + + function selectHint(provider, expectedHint, implicitChar) { + var hintList = expectHints(provider, implicitChar); + expect(hintList.indexOf(expectedHint)).not.toBe(-1); + return provider.insertHint(expectedHint); + } + + // Helper function for testing cursor position + function fixPos(pos) { + if (!("sticky" in pos)) { + pos.sticky = null; + } + return pos; + } + function expectCursorAt(pos) { + var selection = testEditor.getSelection(); + expect(fixPos(selection.start)).toEqual(fixPos(selection.end)); + expect(fixPos(selection.start)).toEqual(fixPos(pos)); + } + + function verifyFirstEntry(hintList, expectedFirstHint) { + expect(hintList[0]).toBe(expectedFirstHint); + } + + // Helper function to + // a) ensure the hintList and the list with the available values have the same size + // b) ensure that all possible values are mentioned in the hintList + function verifyAllValues(hintList, values) { + expect(hintList.length).toBe(values.length); + expect(hintList.sort().toString()).toBe(values.sort().toString()); + } + + + var modesToTest = ['css', 'scss', 'less'], + modeCounter; + + + var selectMode = function () { + return modesToTest[modeCounter]; + }; + + describe("'@' rules in styles mode (selection of correct restricted block based on input)", function () { + + beforeEach(function () { + // create Editor instance (containing a CodeMirror instance) + var mock = SpecRunnerUtils.createMockEditor(defaultContent, selectMode()); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + var testAllHints = function () { + testEditor.setCursorPos({ line: 0, ch: 1 }); // after @ + var hintList = expectHints(CSSAtRuleCodeHints.restrictedBlockHints); + verifyFirstEntry(hintList, "@charset"); // filtered on "empty string" + verifyListsAreIdentical(hintList, ["@charset", + "@font-face", + "@import", + "@keyframes", + "@media", + "@namespace", + "@page", + "@supports"]); + }, + testFilteredHints = function () { + testEditor.setCursorPos({ line: 3, ch: 2 }); // after @m + var hintList = expectHints(CSSAtRuleCodeHints.restrictedBlockHints); + verifyFirstEntry(hintList, "@media"); // filtered on "@m" + verifyListsAreIdentical(hintList, ["@media"]); + }, + testNoHintsOnSpace = function () { + testEditor.setCursorPos({ line: 3, ch: 3 }); // after { + expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, '')).toBe(false); + }, + testNoHints = function () { + testEditor.setCursorPos({ line: 0, ch: 0 }); // after { + expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, 'c')).toBe(false); + }; + + for (modeCounter in modesToTest) { + it("should list all rule hints right after @", testAllHints); + it("should list filtered rule hints right after @m", testFilteredHints); + it("should not list rule hints on space", testNoHintsOnSpace); + it("should not list rule hints if the cursor is before @", testNoHints); + } + }); + + describe("'@' rules in LESS mode (selection of correct restricted block based on input)", function () { + defaultContent = "@ { \n" + + "} \n" + + " \n" + + "@m \n" + + "@green: green;\n" + + ".div { \n" + + "color: @" + + "} \n"; + + beforeEach(function () { + // create Editor instance (containing a CodeMirror instance) + var mock = SpecRunnerUtils.createMockEditor(defaultContent, "less"); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + it("should not list rule hints in less variable evaluation scope", function () { + testEditor.setCursorPos({ line: 3, ch: 3 }); // after { + expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, '')).toBe(false); + }); + + }); + + describe("'@' rule hint insertion", function () { + beforeEach(function () { + // create Editor instance (containing a CodeMirror instance) + var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + it("should insert @rule selected", function () { + testEditor.setCursorPos({ line: 0, ch: 1 }); // cursor after '@' + selectHint(CSSAtRuleCodeHints.restrictedBlockHints, "@charset"); + expect(testDocument.getLine(0)).toBe("@charset { "); + expectCursorAt({ line: 0, ch: 8 }); + }); + + it("should insert filtered selection by replacing the existing rule", function () { + testEditor.setCursorPos({ line: 3, ch: 2 }); // cursor after '@m' + selectHint(CSSAtRuleCodeHints.restrictedBlockHints, "@media"); + expect(testDocument.getLine(3)).toBe("@media "); + expectCursorAt({ line: 3, ch: 6 }); + }); + }); + + }); +}); + diff --git a/src/extensions/default/CSSCodeHints/CSSProperties.json b/src/extensions/default/CSSCodeHints/CSSProperties.json index 4b22076b357..7b80dbca158 100644 --- a/src/extensions/default/CSSCodeHints/CSSProperties.json +++ b/src/extensions/default/CSSCodeHints/CSSProperties.json @@ -2,6 +2,7 @@ "align-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between", "stretch"]}, "align-items": {"values": ["baseline", "center", "flex-end", "flex-start", "stretch"]}, "align-self": {"values": ["auto", "baseline", "center", "flex-end", "flex-start", "stretch"]}, + "all": {"values": []}, "animation": {"values": []}, "animation-delay": {"values": []}, "animation-direction": {"values": ["alternate", "alternate-reverse", "normal", "reverse"]}, @@ -14,7 +15,7 @@ "backface-visibility": {"values": ["hidden", "visible"]}, "background": {"values": []}, "background-attachment": {"values": ["fixed", "local", "scroll", "inherit"]}, - "background-blend-mode": {"values": ["color", "color-burn", "color-dodge", "darken", "difference", "exclusion", "hard-light", "hue", "lighten", "luminosity", "normal", "multiply", "overlay", "saturation", "screen", "soft-light"]}, + "background-blend-mode": {"values": ["color", "color-burn", "color-dodge", "darken", "difference", "exclusion", "hard-light", "hue", "lighten", "luminosity", "multiply", "normal", "overlay", "saturation", "screen", "soft-light"]}, "background-clip": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, "background-color": {"values": ["inherit"], "type": "color"}, "background-image": {"values": ["image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "url()"]}, @@ -55,14 +56,15 @@ "border-top-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "border-top-width": {"values": ["medium", "thin", "thick", "inherit"]}, "border-width": {"values": ["medium", "thin", "thick", "inherit"]}, - "box-decoration-break": {"values": []}, + "box-decoration-break": {"values": ["clone", "slice"]}, "box-shadow": {"values": []}, - "box-sizing": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, + "box-sizing": {"values": ["border-box", "content-box", "inherit"]}, "bottom": {"values": ["auto", "inherit"]}, "break-after": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]}, "break-before": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]}, "break-inside": {"values": ["auto", "avoid", "avoid-column", "avoid-page", "avoid-region"]}, "caption-side": {"values": ["bottom", "top", "inherit"]}, + "caret-color": {"values": ["auto"], "type": "color"}, "clear": {"values": ["both", "left", "none", "right", "inherit"]}, "clip": {"values": ["auto", "inherit"]}, "color": {"values": ["inherit"], "type": "color"}, @@ -81,7 +83,7 @@ "counter-reset": {"values": ["none", "inherit"]}, "cursor": {"values": ["alias", "all-scroll", "auto", "cell", "col-resize", "context-menu", "copy", "crosshair", "default", "e-resize", "ew-resize", "grab", "grabbing", "help", "inherit", "move", "n-resize", "ne-resize", "nesw-resize", "no-drop", "none", "not-allowed", "ns-resize", "nw-resize", "nwse-resize", "pointer", "progress", "row-resize", "s-resize", "se-resize", "sw-resize", "text", "vertical-text", "w-resize", "wait", "zoom-in", "zoom-out"]}, "direction": {"values": ["ltr", "rtl", "inherit"]}, - "display": {"values": ["block", "flex", "grid", "inline", "inline-block", "inline-flex", "inline-grid", "inline-table", "list-item", "none", "run-in", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "inherit"]}, + "display": {"values": ["block", "contents", "flex", "flow-root", "grid", "inline", "inline-block", "inline-flex", "inline-grid", "inline-table", "list-item", "none", "run-in", "subgrid", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "inherit"]}, "empty-cells": {"values": ["hide", "show", "inherit"]}, "filter": {"values": ["blur()", "brightness()", "contrast()", "custom()", "drop-shadow()", "grayscale()", "hue-rotate()", "invert()", "none", "opacity()", "sepia()", "saturate()", "url()"]}, "flex": {"values": ["auto", "initial", "none"]}, @@ -127,13 +129,16 @@ "grid-row-end": {"values": []}, "grid-row-start": {"values": []}, "grid-row-gap": {"values": []}, + "grid-template": {"values": ["none"]}, "grid-template-areas": {"values": []}, "grid-template-columns": {"values": ["auto"]}, "grid-template-rows": {"values": ["auto"]}, + "hanging-punctuation": {"values": ["allow-end", "first", "force-end", "last", "none"]}, "height": {"values": ["auto", "inherit"]}, "hyphens": {"values": ["auto", "manual", "none"]}, "image-orientation": {"values": []}, "image-resolution": {"values": ["from-image", "snap"]}, + "isolation": {"values": ["auto", "isolate"]}, "justify-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between"]}, "left": {"values": ["auto", "inherit"]}, "letter-spacing": {"values": ["normal", "inherit"]}, @@ -151,6 +156,7 @@ "max-width": {"values": ["none", "inherit"]}, "min-height": {"values": ["inherit"]}, "min-width": {"values": ["inherit"]}, + "mix-blend-mode": {"values": ["color", "color-burn", "color-dodge", "darken", "difference", "exclusion", "hard-light", "hue", "lighten", "luminosity", "multiply", "normal", "overlay", "saturation", "screen", "soft-light"]}, "object-fit": {"values": ["contain", "cover", "fill", "none", "scale-down"]}, "object-position": {"values": ["left", "center", "right", "bottom", "top"]}, "opacity": {"values": ["inherit"]}, @@ -183,11 +189,13 @@ "region-fragment": {"values": ["auto", "break"]}, "resize": {"values": ["both", "horizontal", "none", "vertical", "inherit"]}, "right": {"values": ["auto", "inherit"]}, + "scroll-behavior": {"values": ["auto", "smooth"]}, "src": {"values": [ "url()"]}, "shape-image-threshold": {"values": []}, "shape-inside": {"values": ["auto", "circle()", "ellipse()", "inherit", "outside-shape", "polygon()", "rectangle()"]}, "shape-margin": {"values": []}, "shape-outside": {"values": ["none", "inherit", "circle()", "ellipse()", "polygon()", "inset()", "margin-box", "border-box", "padding-box", "content-box", "url()", "image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]}, + "tab-size": {"values": []}, "table-layout": {"values": ["auto", "fixed", "inherit"]}, "text-align": {"values": ["center", "left", "justify", "right", "inherit"]}, "text-align-last": {"values": ["center", "left", "justify", "right", "inherit"]}, @@ -217,6 +225,7 @@ "transition-timing-function": {"values": ["cubic-bezier()", "ease", "ease-in", "ease-in-out", "ease-out", "linear", "step-end", "step-start", "steps()"]}, "unicode-bidi": {"values": ["bidi-override", "embed", "normal", "inherit"]}, "unicode-range": {"values": []}, + "user-select": {"values": ["all", "auto", "contain", "none", "text"]}, "vertical-align": {"values": ["baseline", "bottom", "middle", "sub", "super", "text-bottom", "text-top", "top", "inherit"]}, "visibility": {"values": ["collapse", "hidden", "visible", "inherit"]}, "white-space": {"values": ["normal", "nowrap", "pre", "pre-line", "pre-wrap", "inherit"]}, diff --git a/src/extensions/default/CSSCodeHints/main.js b/src/extensions/default/CSSCodeHints/main.js index 4006d837b2f..8bce6d23ecf 100644 --- a/src/extensions/default/CSSCodeHints/main.js +++ b/src/extensions/default/CSSCodeHints/main.js @@ -468,7 +468,7 @@ define(function (require, exports, module) { AppInit.appReady(function () { var cssPropHints = new CssPropHints(); - CodeHintManager.registerHintProvider(cssPropHints, ["css", "scss", "less"], 0); + CodeHintManager.registerHintProvider(cssPropHints, ["css", "scss", "less"], 1); ExtensionUtils.loadStyleSheet(module, "styles/brackets-css-hints.css"); diff --git a/src/extensions/default/CSSCodeHints/unittests.js b/src/extensions/default/CSSCodeHints/unittests.js index 24dc3a945e4..98dcaeba25a 100644 --- a/src/extensions/default/CSSCodeHints/unittests.js +++ b/src/extensions/default/CSSCodeHints/unittests.js @@ -51,6 +51,21 @@ define(function (require, exports, module) { " bordborder: \n" + " color\n" + "} \n"; + + var defaultHTMLContent = " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + // line 10 + "
\n" + + " \n" + + " \n"; var testDocument, testEditor; @@ -437,6 +452,92 @@ define(function (require, exports, module) { }); }); + + describe("CSS Hint provider in style attribute value context for html mode", function () { + + beforeEach(function () { + // create Editor instance (containing a CodeMirror instance) + var mock = SpecRunnerUtils.createMockEditor(defaultHTMLContent, "html"); + testEditor = mock.editor; + testDocument = mock.doc; + }); + + afterEach(function () { + SpecRunnerUtils.destroyMockEditor(testDocument); + testEditor = null; + testDocument = null; + }); + + it("should list all prop-name hints right after the open quote for style value context", function () { + testEditor.setCursorPos({ line: 4, ch: 12 }); // after "='" + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "align-content"); // filtered on "empty string" + }); + + it("should list all prop-name hints in new line for style value context", function () { + testEditor.setCursorPos({ line: 5, ch: 0 }); + + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "align-content"); // filtered on "empty string" + }); + + it("should list all prop-name hints starting with 'b' in new line for style value context", function () { + testEditor.setCursorPos({ line: 6, ch: 2 }); + + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "backface-visibility"); // filtered on "b" + }); + + it("should list all prop-name hints starting with 'bord' for style value context", function () { + // insert semicolon after previous rule to avoid incorrect tokenizing + testDocument.replaceRange(";", { line: 6, ch: 2 }); + + testEditor.setCursorPos({ line: 7, ch: 5 }); + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "border"); // filtered on "bord" + }); + + it("should list all prop-name hints starting with 'border-' for style value context", function () { + // insert semicolon after previous rule to avoid incorrect tokenizing + testDocument.replaceRange(";", { line: 7, ch: 5 }); + + testEditor.setCursorPos({ line: 8, ch: 8 }); + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "border-bottom"); // filtered on "border-" + }); + + it("should list only prop-name hint border-color for style value context", function () { + // insert semicolon after previous rule to avoid incorrect tokenizing + testDocument.replaceRange(";", { line: 8, ch: 8 }); + + testEditor.setCursorPos({ line: 9, ch: 12 }); + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "border-color"); // filtered on "border-color" + verifyListsAreIdentical(hintList, ["border-color", + "border-left-color", + "border-top-color", + "border-bottom-color", + "border-right-color"]); + }); + + it("should list prop-name hints at end of property-value finished by ; for style value context", function () { + testEditor.setCursorPos({ line: 10, ch: 19 }); // after ; + var hintList = expectHints(CSSCodeHints.cssPropHintProvider); + verifyAttrHints(hintList, "align-content"); // filtered on "empty string" + }); + + it("should NOT list prop-name hints right before style value context", function () { + testEditor.setCursorPos({ line: 4, ch: 11 }); // after = + expectNoHints(CSSCodeHints.cssPropHintProvider); + }); + + it("should NOT list prop-name hints after style value context", function () { + testEditor.setCursorPos({ line: 10, ch: 20 }); // after "'" + expectNoHints(CSSCodeHints.cssPropHintProvider); + }); + + }); + describe("CSS hint provider in other filecontext (e.g. javascript)", function () { var defaultContent = "function foobar (args) { \n " + diff --git a/src/extensions/default/CSSPseudoSelectorHints/PseudoSelectors.json b/src/extensions/default/CSSPseudoSelectorHints/PseudoSelectors.json new file mode 100644 index 00000000000..e6a4ed7ce40 --- /dev/null +++ b/src/extensions/default/CSSPseudoSelectorHints/PseudoSelectors.json @@ -0,0 +1,52 @@ +{ + "selectors": + { + "active": {"desc": "Selects the active link"}, + "checked": {"desc": "Selects every checked element"}, + "default": {"desc": "Selects every UI element that is the default among a group of similar elements"}, + "dir(direction)": {"desc": "Selects every element whose text direction is 'direction'", "text": "dir()"}, + "disabled": {"desc": "Selects every disabled element"}, + "empty": {"desc": "Selects every element that has no children/text (including text nodes)"}, + "enabled": {"desc": "Selects every enabled element"}, + "first-child": {"desc": "Selects every element that is the first child of its parent"}, + "first-of-type": {"desc": "Selects every element that is the first element identified by 'type' of its parent"}, + "focus": {"desc": "Selects the input element which has focus"}, + "focus-within": {"desc": "Selects every element which or whose descendant has focus"}, + "fullscreen": {"desc": "Selects the element being in full-screen mode"}, + "hover": {"desc": "Selects elements on mouse over"}, + "in-range": {"desc": "Selects input elements with a value within a specified range"}, + "indeterminate": {"desc": "Selects every indeterminate checkbox or radio button"}, + "invalid": {"desc": "Selects all input elements with an invalid value"}, + "lang(language)": {"desc": "Selects every element with a lang attribute equal to 'language'", "text": "lang()"}, + "last-child": {"desc": "Selects every element that is the last child of its parent"}, + "last-of-type": {"desc": "Selects every element that is the last element of its parent"}, + "link": {"desc": "Selects all unvisited links"}, + "matches(selectors)": {"desc": "Selects every element that is matched by one or more selectors in the 'selectors' list", "text": "matches()"}, + "not(selector)": {"desc": "Selects every element that is not an element identified by 'selector'", "text": "not()"}, + "nth-child(n)": {"desc": "Selects every element that is the second child of its parent", "text": "nth-child()"}, + "nth-last-child(n)": {"desc": "Selects every element that is the second child of its parent, counting from the last child", "text": "nth-last-child()"}, + "nth-last-of-type(n)": {"desc": "Selects every element that is the nth element of its parent, counting from the last child", "text": "nth-last-of-type()"}, + "nth-of-type(n)": {"desc": "Selects every element that is the nth element of its parent", "text": "nth-of-type(n)"}, + "only-child": {"desc": "Selects every element that is the only child of its parent"}, + "only-of-type": {"desc": "Selects every element that is the only element of this type of its parent"}, + "optional": {"desc": "Selects input elements with no 'required' attribute"}, + "out-of-range": {"desc": "Selects input elements with a value outside a specified range"}, + "placeholder-shown": {"desc": "Selects all and