mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
Merge branch 'fb-pw-simple-project-config' of github.com:atom/atom into fb-pw-simple-project-config
This commit is contained in:
@@ -70,6 +70,11 @@ repeat these steps to upgrade to future releases.
|
||||
* [macOS](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac)
|
||||
* [Windows](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows)
|
||||
|
||||
## Discussion
|
||||
|
||||
* Discuss Atom on our [forums](https://discuss.atom.io/)
|
||||
* Chat about Atom on our Slack team -- [instructions for joining](https://discuss.atom.io/t/join-us-on-slack/16638?source_topic_id=25406)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/atom/atom/blob/master/LICENSE.md)
|
||||
|
||||
55
docs/focus/2018-02-12.md
Normal file
55
docs/focus/2018-02-12.md
Normal file
@@ -0,0 +1,55 @@
|
||||
## Highlights from the past week
|
||||
|
||||
- Atom IDE
|
||||
- Started conversion of atom-languageclient to TypeScript [atom/atom-languageclient#175](https://github.com/atom/atom-languageclient/pull/175)
|
||||
- @atom/watcher
|
||||
- Report events related to [symlinks](https://github.com/atom/watcher/pull/111) and [test for symlink-related edge cases.](https://github.com/atom/watcher/pull/114)
|
||||
- Produce filesystem events with a [consistent parent path](https://github.com/atom/watcher/pull/113) to the one used to create a watcher, even if the watcher was created with a a path containing symlinks.
|
||||
- Verified correct behavior with regard to [filesystem case sensitivity.](https://github.com/atom/watcher/pull/116)
|
||||
- Corrected buggy [utf8 to utf16 conversion](https://github.com/atom/watcher/pull/115) on Windows.
|
||||
- Ran through the MacOS cases in the [testing matrix.](https://github.com/atom/atom/pull/16124)
|
||||
- Set up a Samba share on @ungb's testing server to exercise Samba network drives.
|
||||
- Published version 1.0.0 on [npm.](https://www.npmjs.com/package/@atom/watcher)
|
||||
- GitHub Package
|
||||
- Introduce a package configuration option to [disable the in-editor merge conflict resolution.](https://github.com/atom/github/pull/1305)
|
||||
- Published a new release v0.10.0
|
||||
- Investigated and spiked on a fix for amending bug in single-commit repos, which was surfaced by failing cache invalidation tests that were blocking release
|
||||
- Deferred fixing underlying bug - [atom/github#1303](https://github.com/atom/github/issues/1303)
|
||||
- Fixed failing tests - [atom/github#1302](https://github.com/atom/github/pull/1302)
|
||||
- Teletype
|
||||
- Released [Teletype 0.7.0](https://github.com/atom/teletype/releases/tag/v0.7.0) with improved diagnostics for errors that occur during package initialization ([atom/teletype#266](https://github.com/atom/teletype/issues/266), [atom/teletype#297](https://github.com/atom/teletype/issues/297))
|
||||
- Opened [atom/teletype#323](https://github.com/atom/teletype/pull/323), [atom/teletype-client#52](https://github.com/atom/teletype-client/pull/52), and [atom/fuzzy-finder#335](https://github.com/atom/fuzzy-finder/pull/335) to pave the way for guests to use the fuzzy-finder to open any remote editor shared by the host ([atom/teletype#268](https://github.com/atom/teletype/issues/268))
|
||||
|
||||
## Focus for week ahead
|
||||
|
||||
- Atom IDE
|
||||
- Finish conversion of atom-languageclient to TypeScript [atom/atom-languageclient#175](https://github.com/atom/atom-languageclient/pull/175)
|
||||
- Contribute TypeScript type definitions for Atom IDE to [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped)
|
||||
- Contribute missing TypeScript type defintions for Atom to [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/atom)
|
||||
- @atom/watcher
|
||||
- Complete [the testing matrix](https://github.com/atom/atom/pull/16124) on Linux and Windows.
|
||||
- :shipit: Merge [@atom/watcher support]((https://github.com/atom/atom/pull/16124)) into Atom _(as a non-default `PathWatcher` backend)_. :shipit:
|
||||
- GitHub Package
|
||||
- Quarterly planning. Which might change all of these :wink:
|
||||
- Finish tracking down our [freezing CI builds.](https://github.com/atom/github/pull/1289)
|
||||
- Resurrect the [gargantuan credential helper and GPG pinentry refactoring PR](https://github.com/atom/github/pull/846) and see how much work is needed to get it over the finish line.
|
||||
- Fix issue with diff view popping up unexpectedly - [atom/github#1287](https://github.com/atom/github/issues/1287)
|
||||
- Teletype
|
||||
- Complete initial implementation and merge pull requests ([atom/teletype#323](https://github.com/atom/teletype/pull/323), [atom/teletype-client#52](https://github.com/atom/teletype-client/pull/52), and [atom/fuzzy-finder#335](https://github.com/atom/fuzzy-finder/pull/335)) allowing guests to use the fuzzy-finder to open any remote editor shared by the host ([atom/teletype#268](https://github.com/atom/teletype/issues/268))
|
||||
- Use fuzzy-finder support internally in our day-to-day workflows to assess usability
|
||||
- Tree-sitter
|
||||
- Finish and merge [tree-sitter/tree-sitter#128](https://github.com/tree-sitter/tree-sitter/pull/128), which fixes a fundamental performance problem when editing large files.
|
||||
- Fix syntax highlighting bugs [#16643](https://github.com/atom/atom/issues/16643) and [#16642](https://github.com/atom/atom/issues/16642).
|
||||
- Fix [#16621](https://github.com/atom/atom/issues/16621) - snippets not working when using Tree-sitter.
|
||||
- Xray
|
||||
* @nathansobo (and @as-cii part time) will be focusing the next 12 weeks on a prototype for [a new Electron-based text editor](https://github.com/atom/xray). The goal is to explore the viability of radical performance improvements that could be possible if we make breaking changes to Atom's APIs. At the end of the 12 weeks, we will reassess our plans based on what we have managed to learn and accomplish.
|
||||
* Week 1 of 12
|
||||
* Clarify and document goals for the next 12 weeks.
|
||||
* Ensure that the guide matches our current plans.
|
||||
* Refine WebGL based text rendering.
|
||||
* Make sure ASCII text renders correctly without being clipped
|
||||
* Render text correctly on high DPI displays
|
||||
* Use correct API for texture atlas updates
|
||||
* Add mouse-wheel scrolling support
|
||||
* Non-ASCII rendering, using the HarfBuzz text shaping library to detect combining characters
|
||||
* Stretch goal: Switch document encoding to UTF-8 for memory compactness and support multi-byte-aware character indexing.
|
||||
51
docs/focus/2018-02-19.md
Normal file
51
docs/focus/2018-02-19.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## Highlights from the past week
|
||||
|
||||
- Atom IDE
|
||||
- Converted atom-languageclient to TypeScript
|
||||
- ide-typescript updated to use TypeScript 2.7.2
|
||||
- Published updates to ide-typescript, ide-json, and ide-csharp to improve language server stability
|
||||
- @atom/watcher
|
||||
- Gracefully handle the situation where a network share with a watch root is disconnected ([#119](https://github.com/atom/watcher/pull/119))
|
||||
- Merged into Atom master behind a feature flag ([#16124](https://github.com/atom/atom/pull/16124)) just after the 1.24.0 / 1.25.0-beta0 release
|
||||
- Fixed a crash when messages are sent to the worker thread before it's properly initialized ([atom/watcher#121](https://github.com/atom/watcher/pull/121))
|
||||
- GitHub Package
|
||||
- Investigate intermittently freezing tests on Travis in [atom/github#1289](https://github.com/atom/github/pull/1289). Not much luck so far
|
||||
- Fixed issue with diff views popping up unexpectedly [atom/github#1311](https://github.com/atom/github/pull/1311). Just waiting on review
|
||||
- Teletype
|
||||
- Fixed an unanticipated bug that would cause non-existent selections to appear in the editor of other participants ([atom/teletype#326](https://github.com/atom/teletype/pull/326)).
|
||||
- Published [version 0.8.0](https://github.com/atom/teletype/releases/tag/v0.8.0).
|
||||
- Refactored teletype-client and simplified how added/removed editors are broadcasted to participants ([atom/teletype-client#52](https://github.com/atom/teletype-client/pull/52)).
|
||||
- Polished the design of fuzzy-finder ([atom/fuzzy-finder#335](https://github.com/atom/fuzzy-finder/pull/335))
|
||||
- Pushed [atom/teletype#323](https://github.com/atom/teletype/pull/323) over the finish line.
|
||||
- Xray
|
||||
- We made a slight change of plans and decided to spend more time clarifying the overall vision for the project.
|
||||
- We have a [branch](https://github.com/atom/xray/tree/roadmap) with a new README that matches our current thinking, but the Q1 roadmap is still in progress.
|
||||
- We did manage to get text rendering with retina displays and non-clipped characters, but there's still work to do. We are also experimenting populating our glyph atlas with up to 4 variants of each glyph at different subpixel positions to more closely match text rendered purely on the CPU.
|
||||
- Tree-sitter
|
||||
- Took some time to fix unrelated regressions from the bug-bash month
|
||||
- Fixed a bug where atom --wait did not work correctly on Windows (#16740)
|
||||
- Fixed a bug that prevented Atom from reusing an existing window when the same path was opened twice (#16764)
|
||||
- Fixed regressions in the behavior of the atom.textEditors.getGrammarOverride and atom.grammars.loadGrammar methods (#16733, #16747)
|
||||
- Fixed several syntax highlighting bugs (#16642, #16643)
|
||||
## Focus for week ahead
|
||||
|
||||
- Atom IDE
|
||||
- Investigate new Atom IDE UI features for rename operations and workspace symbol search
|
||||
- Publish TypeScript definitions for atom-ide/atom-languageclient to DefinitelyTyped
|
||||
- Wire up atom-ide-ui console to LSP server logging
|
||||
- @atom/watcher
|
||||
- Diagnose and correct crashes and lock-ups as people report them
|
||||
- GitHub Package
|
||||
- Establish high-level goals and scope bounds for the GitHub side of the integration
|
||||
- Document a protocol for the evolution of major features: ensure they contribute to a cohesive experience with the rest of the package, make sure that @simurai is looped in to the conversation, make sure the community has visibility to our goals
|
||||
- Show recent commits in Git panel
|
||||
- Teletype
|
||||
- Merge and use [atom/fuzzy-finder#335](https://github.com/atom/fuzzy-finder/pull/335), [atom/teletype-client#52](https://github.com/atom/teletype-client/pull/52) and [atom/teletype#323](https://github.com/atom/teletype/pull/323).
|
||||
- Publish Teletype v0.9.0 containing the new fuzzy-finder support.
|
||||
- Tree-sitter
|
||||
- Fix an issue where snippets are not available when using tree-sitter (#16621)
|
||||
- Start work on optimizing editing in the presence of large parse errors (#16590)
|
||||
- Start work on allowing parsing to take place on a background thread
|
||||
- Xray
|
||||
- We will continue clarifying the overall vision with a focus on real time collaboration. This may extend beyond the scope of Xray, but is important to get clarity on before comitting to a roadmap.
|
||||
- We hope to iron out the remaining issues with subpixel-positioning of glyphs to more faithfully reproduce Chrome's behavior when rendering text via the normal DOM-based code path.
|
||||
57
package.json
57
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "1.25.0-dev",
|
||||
"version": "1.26.0-dev",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/main-process/main.js",
|
||||
"repository": {
|
||||
@@ -15,9 +15,10 @@
|
||||
"electronVersion": "1.7.11",
|
||||
"dependencies": {
|
||||
"@atom/nsfw": "^1.0.18",
|
||||
"@atom/watcher": "1.0.3",
|
||||
"@atom/source-map-support": "^0.3.4",
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "8.2.9",
|
||||
"atom-keymap": "8.2.10",
|
||||
"atom-select-list": "^0.7.0",
|
||||
"atom-ui": "0.4.1",
|
||||
"babel-core": "5.8.38",
|
||||
@@ -38,7 +39,7 @@
|
||||
"fs-plus": "^3.0.1",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"git-utils": "5.2.1",
|
||||
"git-utils": "5.3.1",
|
||||
"glob": "^7.1.1",
|
||||
"grim": "1.5.0",
|
||||
"jasmine-json": "~0.0",
|
||||
@@ -71,7 +72,7 @@
|
||||
"sinon": "1.17.4",
|
||||
"temp": "^0.8.3",
|
||||
"text-buffer": "13.11.8",
|
||||
"tree-sitter": "^0.8.6",
|
||||
"tree-sitter": "^0.9.2",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"winreg": "^1.2.1",
|
||||
@@ -79,41 +80,41 @@
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.29.0",
|
||||
"atom-dark-ui": "0.53.1",
|
||||
"atom-dark-ui": "0.53.2",
|
||||
"atom-light-syntax": "0.29.0",
|
||||
"atom-light-ui": "0.46.1",
|
||||
"atom-light-ui": "0.46.2",
|
||||
"base16-tomorrow-dark-theme": "1.5.0",
|
||||
"base16-tomorrow-light-theme": "1.5.0",
|
||||
"one-dark-ui": "1.10.10",
|
||||
"one-light-ui": "1.10.10",
|
||||
"one-dark-ui": "1.10.11",
|
||||
"one-light-ui": "1.10.11",
|
||||
"one-dark-syntax": "1.8.2",
|
||||
"one-light-syntax": "1.8.2",
|
||||
"solarized-dark-syntax": "1.1.4",
|
||||
"solarized-light-syntax": "1.1.4",
|
||||
"about": "1.8.0",
|
||||
"archive-view": "0.64.2",
|
||||
"archive-view": "0.64.3",
|
||||
"autocomplete-atom-api": "0.10.7",
|
||||
"autocomplete-css": "0.17.5",
|
||||
"autocomplete-html": "0.8.4",
|
||||
"autocomplete-plus": "2.40.2",
|
||||
"autocomplete-plus": "2.40.4",
|
||||
"autocomplete-snippets": "1.12.0",
|
||||
"autoflow": "0.29.3",
|
||||
"autosave": "0.24.6",
|
||||
"background-tips": "0.27.1",
|
||||
"background-tips": "0.28.0",
|
||||
"bookmarks": "0.45.1",
|
||||
"bracket-matcher": "0.89.1",
|
||||
"command-palette": "0.43.0",
|
||||
"command-palette": "0.43.4",
|
||||
"dalek": "0.2.1",
|
||||
"deprecation-cop": "0.56.9",
|
||||
"dev-live-reload": "0.48.1",
|
||||
"encoding-selector": "0.23.8",
|
||||
"exception-reporting": "0.42.0",
|
||||
"exception-reporting": "0.43.1",
|
||||
"find-and-replace": "0.215.5",
|
||||
"fuzzy-finder": "1.7.5",
|
||||
"github": "0.9.1",
|
||||
"fuzzy-finder": "1.7.6",
|
||||
"github": "0.10.1",
|
||||
"git-diff": "1.3.9",
|
||||
"go-to-line": "0.33.0",
|
||||
"grammar-selector": "0.49.9",
|
||||
"grammar-selector": "0.50.0",
|
||||
"image-view": "0.62.4",
|
||||
"incompatible-packages": "0.27.3",
|
||||
"keybinding-resolver": "0.38.1",
|
||||
@@ -124,8 +125,8 @@
|
||||
"notifications": "0.70.2",
|
||||
"open-on-github": "1.3.1",
|
||||
"package-generator": "1.3.0",
|
||||
"settings-view": "0.254.0",
|
||||
"snippets": "1.3.0",
|
||||
"settings-view": "0.254.1",
|
||||
"snippets": "1.3.1",
|
||||
"spell-check": "0.72.7",
|
||||
"status-bar": "1.8.15",
|
||||
"styleguide": "0.49.10",
|
||||
@@ -137,39 +138,39 @@
|
||||
"welcome": "0.36.6",
|
||||
"whitespace": "0.37.5",
|
||||
"wrap-guide": "0.40.3",
|
||||
"language-c": "0.59.1",
|
||||
"language-c": "0.59.2",
|
||||
"language-clojure": "0.22.7",
|
||||
"language-coffee-script": "0.49.3",
|
||||
"language-csharp": "0.14.4",
|
||||
"language-csharp": "1.0.1",
|
||||
"language-css": "0.42.10",
|
||||
"language-gfm": "0.90.3",
|
||||
"language-git": "0.19.1",
|
||||
"language-go": "0.45.0",
|
||||
"language-go": "0.45.2",
|
||||
"language-html": "0.49.0",
|
||||
"language-hyperlink": "0.16.3",
|
||||
"language-java": "0.28.0",
|
||||
"language-javascript": "0.128.1",
|
||||
"language-javascript": "0.128.3",
|
||||
"language-json": "0.19.1",
|
||||
"language-less": "0.34.2",
|
||||
"language-make": "0.22.3",
|
||||
"language-mustache": "0.14.4",
|
||||
"language-mustache": "0.14.5",
|
||||
"language-objective-c": "0.15.1",
|
||||
"language-perl": "0.38.1",
|
||||
"language-php": "0.43.0",
|
||||
"language-php": "0.43.1",
|
||||
"language-property-list": "0.9.1",
|
||||
"language-python": "0.48.0",
|
||||
"language-python": "0.49.2",
|
||||
"language-ruby": "0.71.4",
|
||||
"language-ruby-on-rails": "0.25.3",
|
||||
"language-sass": "0.61.4",
|
||||
"language-shellscript": "0.26.0",
|
||||
"language-shellscript": "0.26.1",
|
||||
"language-source": "0.9.0",
|
||||
"language-sql": "0.25.10",
|
||||
"language-text": "0.7.3",
|
||||
"language-todo": "0.29.4",
|
||||
"language-toml": "0.18.2",
|
||||
"language-typescript": "0.3.0",
|
||||
"language-typescript": "0.3.2",
|
||||
"language-xml": "0.35.2",
|
||||
"language-yaml": "0.31.1"
|
||||
"language-yaml": "0.31.2"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -32,6 +32,8 @@ module.exports = function (packagedAppPath) {
|
||||
relativePath.endsWith(path.join('node_modules', 'graceful-fs', 'graceful-fs.js')) ||
|
||||
relativePath.endsWith(path.join('node_modules', 'htmlparser2', 'lib', 'index.js')) ||
|
||||
relativePath.endsWith(path.join('node_modules', 'minimatch', 'minimatch.js')) ||
|
||||
relativePath.endsWith(path.join('node_modules', 'request', 'index.js')) ||
|
||||
relativePath.endsWith(path.join('node_modules', 'request', 'request.js')) ||
|
||||
relativePath === path.join('..', 'exports', 'atom.js') ||
|
||||
relativePath === path.join('..', 'src', 'electron-shims.js') ||
|
||||
relativePath === path.join('..', 'src', 'safe-clipboard.js') ||
|
||||
@@ -50,7 +52,6 @@ module.exports = function (packagedAppPath) {
|
||||
relativePath === path.join('..', 'node_modules', 'node-fetch', 'lib', 'fetch-error.js') ||
|
||||
relativePath === path.join('..', 'node_modules', 'superstring', 'index.js') ||
|
||||
relativePath === path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') ||
|
||||
relativePath === path.join('..', 'node_modules', 'request', 'index.js') ||
|
||||
relativePath === path.join('..', 'node_modules', 'resolve', 'index.js') ||
|
||||
relativePath === path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') ||
|
||||
relativePath === path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') ||
|
||||
|
||||
@@ -1,64 +1,63 @@
|
||||
'use strict'
|
||||
|
||||
const csslint = require('csslint').CSSLint
|
||||
const expandGlobPaths = require('./expand-glob-paths')
|
||||
const LessCache = require('less-cache')
|
||||
const stylelint = require('stylelint')
|
||||
const path = require('path')
|
||||
const readFiles = require('./read-files')
|
||||
|
||||
const CONFIG = require('../config')
|
||||
const LESS_CACHE_VERSION = require('less-cache/package.json').version
|
||||
|
||||
module.exports = function () {
|
||||
const globPathsToLint = [
|
||||
path.join(CONFIG.repositoryRootPath, 'static/**/*.less')
|
||||
]
|
||||
const lintOptions = {
|
||||
'adjoining-classes': false,
|
||||
'duplicate-background-images': false,
|
||||
'box-model': false,
|
||||
'box-sizing': false,
|
||||
'bulletproof-font-face': false,
|
||||
'compatible-vendor-prefixes': false,
|
||||
'display-property-grouping': false,
|
||||
'duplicate-properties': false,
|
||||
'fallback-colors': false,
|
||||
'font-sizes': false,
|
||||
'gradients': false,
|
||||
'ids': false,
|
||||
'important': false,
|
||||
'known-properties': false,
|
||||
'order-alphabetical': false,
|
||||
'outline-none': false,
|
||||
'overqualified-elements': false,
|
||||
'regex-selectors': false,
|
||||
'qualified-headings': false,
|
||||
'unique-headings': false,
|
||||
'universal-selector': false,
|
||||
'vendor-prefix': false
|
||||
}
|
||||
for (let rule of csslint.getRules()) {
|
||||
if (!lintOptions.hasOwnProperty(rule.id)) lintOptions[rule.id] = true
|
||||
}
|
||||
const lessCache = new LessCache({
|
||||
cacheDir: path.join(CONFIG.intermediateAppPath, 'less-compile-cache'),
|
||||
fallbackDir: path.join(CONFIG.atomHomeDirPath, 'compile-cache', 'prebuild-less', LESS_CACHE_VERSION),
|
||||
syncCaches: true,
|
||||
resourcePath: CONFIG.repositoryRootPath,
|
||||
importPaths: [
|
||||
path.join(CONFIG.intermediateAppPath, 'static', 'variables'),
|
||||
path.join(CONFIG.intermediateAppPath, 'static')
|
||||
]
|
||||
})
|
||||
return expandGlobPaths(globPathsToLint).then(readFiles).then((files) => {
|
||||
const errors = []
|
||||
for (let file of files) {
|
||||
const css = lessCache.cssForFile(file.path, file.content)
|
||||
const result = csslint.verify(css, lintOptions)
|
||||
for (let message of result.messages) {
|
||||
errors.push({path: file.path.replace(/\.less$/, '.css'), lineNumber: message.line, message: message.message, rule: message.rule.id})
|
||||
return stylelint
|
||||
.lint({
|
||||
files: path.join(CONFIG.repositoryRootPath, 'static/**/*.less'),
|
||||
configBasedir: __dirname,
|
||||
configFile: path.resolve(__dirname, '..', '..', 'stylelint.config.js')
|
||||
})
|
||||
.then(({results}) => {
|
||||
const errors = []
|
||||
|
||||
for (const result of results) {
|
||||
for (const deprecation of result.deprecations) {
|
||||
console.log('stylelint encountered deprecation:', deprecation.text)
|
||||
if (deprecation.reference != null) {
|
||||
console.log('more information at', deprecation.reference)
|
||||
}
|
||||
}
|
||||
|
||||
for (const invalidOptionWarning of result.invalidOptionWarnings) {
|
||||
console.warn(
|
||||
'stylelint encountered invalid option:',
|
||||
invalidOptionWarning.text
|
||||
)
|
||||
}
|
||||
|
||||
if (result.errored) {
|
||||
for (const warning of result.warnings) {
|
||||
if (warning.severity === 'error') {
|
||||
errors.push({
|
||||
path: result.source,
|
||||
lineNumber: warning.line,
|
||||
message: warning.text,
|
||||
rule: warning.rule
|
||||
})
|
||||
} else {
|
||||
console.warn(
|
||||
'stylelint encountered non-critical warning in file',
|
||||
result.source,
|
||||
'at line',
|
||||
warning.line,
|
||||
'for rule',
|
||||
warning.rule + ':',
|
||||
warning.text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
})
|
||||
|
||||
return errors
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('There was a problem linting LESS:')
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"babel-core": "5.8.38",
|
||||
"coffeelint": "1.15.7",
|
||||
"colors": "1.1.2",
|
||||
"csslint": "1.0.2",
|
||||
"donna": "1.0.16",
|
||||
"electron-chromedriver": "~1.7",
|
||||
"electron-link": "0.1.2",
|
||||
@@ -31,8 +30,10 @@
|
||||
"season": "5.3.0",
|
||||
"semver": "5.3.0",
|
||||
"standard": "8.4.0",
|
||||
"stylelint": "^9.0.0",
|
||||
"stylelint-config-standard": "^18.1.0",
|
||||
"sync-request": "3.0.1",
|
||||
"tello": "1.0.5",
|
||||
"tello": "1.0.7",
|
||||
"webdriverio": "2.4.5",
|
||||
"yargs": "4.8.1"
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('Config', () => {
|
||||
|
||||
describe('when the first component of the scope descriptor matches a legacy scope alias', () =>
|
||||
it('falls back to properties defined for the legacy scope if no value is found for the original scope descriptor', () => {
|
||||
atom.config.addLegacyScopeAlias('javascript', '.source.js')
|
||||
atom.config.setLegacyScopeAliasForNewScope('javascript', '.source.js')
|
||||
atom.config.set('foo', 100, {scopeSelector: '.source.js'})
|
||||
atom.config.set('foo', 200, {scopeSelector: 'javascript for_statement'})
|
||||
|
||||
@@ -150,7 +150,7 @@ describe('Config', () => {
|
||||
|
||||
describe('when the first component of the scope descriptor matches a legacy scope alias', () =>
|
||||
it('includes the values defined for the legacy scope', () => {
|
||||
atom.config.addLegacyScopeAlias('javascript', '.source.js')
|
||||
atom.config.setLegacyScopeAliasForNewScope('javascript', '.source.js')
|
||||
|
||||
expect(atom.config.set('foo', 41)).toBe(true)
|
||||
expect(atom.config.set('foo', 42, {scopeSelector: 'javascript'})).toBe(true)
|
||||
|
||||
@@ -333,3 +333,46 @@ describe "ContextMenuManager", ->
|
||||
}
|
||||
]
|
||||
])
|
||||
|
||||
describe "::templateForEvent(target) (sorting)", ->
|
||||
it "applies simple sorting rules", ->
|
||||
contextMenu.add('.parent': [{
|
||||
label: 'My Command',
|
||||
command: "test:my-command",
|
||||
after: ["test:my-other-command"]
|
||||
}, {
|
||||
label: 'My Other Command',
|
||||
command: "test:my-other-command",
|
||||
}])
|
||||
dispatchedEvent = {target: parent}
|
||||
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([{
|
||||
label: 'My Other Command',
|
||||
command: 'test:my-other-command',
|
||||
}, {
|
||||
label: 'My Command',
|
||||
command: 'test:my-command',
|
||||
after: ["test:my-other-command"]
|
||||
}])
|
||||
|
||||
it "applies sorting rules recursively to submenus", ->
|
||||
contextMenu.add('.parent': [{
|
||||
submenu: [{
|
||||
label: 'My Command',
|
||||
command: "test:my-command",
|
||||
after: ["test:my-other-command"]
|
||||
}, {
|
||||
label: 'My Other Command',
|
||||
command: "test:my-other-command",
|
||||
}]
|
||||
}])
|
||||
dispatchedEvent = {target: parent}
|
||||
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([{
|
||||
submenu: [{
|
||||
label: 'My Other Command',
|
||||
command: 'test:my-other-command',
|
||||
}, {
|
||||
label: 'My Command',
|
||||
command: 'test:my-command',
|
||||
after: ["test:my-other-command"]
|
||||
}]
|
||||
}])
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('GrammarRegistry', () => {
|
||||
const buffer = new TextBuffer()
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe(true)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js')
|
||||
expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe('source.js')
|
||||
|
||||
// Returns true if we found the grammar, even if it didn't change
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe(true)
|
||||
@@ -47,6 +48,7 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, null)).toBe(true)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar')
|
||||
expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
243
spec/menu-sort-helpers-spec.js
Normal file
243
spec/menu-sort-helpers-spec.js
Normal file
@@ -0,0 +1,243 @@
|
||||
const {sortMenuItems} = require('../src/menu-sort-helpers')
|
||||
|
||||
describe('contextMenu', () => {
|
||||
describe('dedupes separators', () => {
|
||||
it('preserves existing submenus', () => {
|
||||
const items = [{ submenu: [] }]
|
||||
expect(sortMenuItems(items)).toEqual(items)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dedupes separators', () => {
|
||||
it('trims leading separators', () => {
|
||||
const items = [{ type: 'separator' }, { command: 'core:one' }]
|
||||
const expected = [{ command: 'core:one' }]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('preserves separators at the begining of set two', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' }, { command: 'core:two' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('trims trailing separators', () => {
|
||||
const items = [{ command: 'core:one' }, { type: 'separator' }]
|
||||
const expected = [{ command: 'core:one' }]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('removes duplicate separators across sets', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' }, { type: 'separator' },
|
||||
{ type: 'separator' }, { command: 'core:two' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('can move an item to a different group by merging groups', () => {
|
||||
it('can move a group of one item', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:three', after: ['core:one'] },
|
||||
{ type: 'separator' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
{ command: 'core:three', after: ['core:one'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it("moves all items in the moving item's group", () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:three', after: ['core:one'] },
|
||||
{ command: 'core:four' },
|
||||
{ type: 'separator' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
{ command: 'core:three', after: ['core:one'] },
|
||||
{ command: 'core:four' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it("ignores positions relative to commands that don't exist", () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:three', after: ['core:does-not-exist'] },
|
||||
{ command: 'core:four', after: ['core:one'] },
|
||||
{ type: 'separator' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
{ command: 'core:three', after: ['core:does-not-exist'] },
|
||||
{ command: 'core:four', after: ['core:one'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can handle recursive group merging', () => {
|
||||
const items = [
|
||||
{ command: 'core:one', after: ['core:three'] },
|
||||
{ command: 'core:two', before: ['core:one'] },
|
||||
{ command: 'core:three' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:three' },
|
||||
{ command: 'core:two', before: ['core:one'] },
|
||||
{ command: 'core:one', after: ['core:three'] }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can merge multiple groups when given a list of before/after commands', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:three', after: ['core:one', 'core:two'] }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:two' },
|
||||
{ command: 'core:one' },
|
||||
{ command: 'core:three', after: ['core:one', 'core:two'] }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can merge multiple groups based on both before/after commands', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:three', after: ['core:one'], before: ['core:two'] }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
{ command: 'core:three', after: ['core:one'], before: ['core:two'] },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sorts items within their ultimate group', () => {
|
||||
it('does a simple sort', () => {
|
||||
const items = [
|
||||
{ command: 'core:two', after: ['core:one'] },
|
||||
{ command: 'core:one' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual([
|
||||
{ command: 'core:one' },
|
||||
{ command: 'core:two', after: ['core:one'] }
|
||||
])
|
||||
})
|
||||
|
||||
it('resolves cycles by ignoring things that conflict', () => {
|
||||
const items = [
|
||||
{ command: 'core:two', after: ['core:one'] },
|
||||
{ command: 'core:one', after: ['core:two'] }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual([
|
||||
{ command: 'core:one', after: ['core:two'] },
|
||||
{ command: 'core:two', after: ['core:one'] }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('sorts groups', () => {
|
||||
it('does a simple sort', () => {
|
||||
const items = [
|
||||
{ command: 'core:two', afterGroupContaining: ['core:one'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:one' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual([
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two', afterGroupContaining: ['core:one'] }
|
||||
])
|
||||
})
|
||||
|
||||
it('resolves cycles by ignoring things that conflict', () => {
|
||||
const items = [
|
||||
{ command: 'core:two', afterGroupContaining: ['core:one'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:one', afterGroupContaining: ['core:two'] }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual([
|
||||
{ command: 'core:one', afterGroupContaining: ['core:two'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two', afterGroupContaining: ['core:one'] }
|
||||
])
|
||||
})
|
||||
|
||||
it('ignores references to commands that do not exist', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
command: 'core:two',
|
||||
afterGroupContaining: ['core:does-not-exist']
|
||||
}
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual([
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two', afterGroupContaining: ['core:does-not-exist'] }
|
||||
])
|
||||
})
|
||||
|
||||
it('only respects the first matching [before|after]GroupContaining rule in a given group', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:three', beforeGroupContaining: ['core:one'] },
|
||||
{ command: 'core:four', afterGroupContaining: ['core:two'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
expect(sortMenuItems(items)).toEqual([
|
||||
{ command: 'core:three', beforeGroupContaining: ['core:one'] },
|
||||
{ command: 'core:four', afterGroupContaining: ['core:two'] },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -905,6 +905,46 @@ describe('TextEditorComponent', () => {
|
||||
expect(component.getLineNumberGutterWidth()).toBe(originalLineNumberGutterWidth)
|
||||
})
|
||||
|
||||
it('gracefully handles edits that change the maxScrollTop by causing the horizontal scrollbar to disappear', async () => {
|
||||
const rowsPerTile = 1
|
||||
const {component, element, editor} = buildComponent({rowsPerTile, autoHeight: false})
|
||||
|
||||
await setEditorHeightInLines(component, 1)
|
||||
await setEditorWidthInCharacters(component, 7)
|
||||
|
||||
// Updating scrollbar styles.
|
||||
const style = document.createElement('style')
|
||||
style.textContent = '::-webkit-scrollbar { height: 17px; width: 10px; }'
|
||||
jasmine.attachToDOM(style)
|
||||
TextEditor.didUpdateScrollbarStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
element.focus()
|
||||
component.setScrollTop(component.measurements.lineHeight)
|
||||
|
||||
component.scheduleUpdate()
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
editor.setSelectedBufferRange([[0, 1], [12, 2]])
|
||||
editor.backspace()
|
||||
|
||||
// component.scheduleUpdate()
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
expect(component.getScrollTop()).toBe(0)
|
||||
|
||||
const renderedLines = queryOnScreenLineElements(element).sort((a, b) => a.dataset.screenRow - b.dataset.screenRow)
|
||||
const renderedLineNumbers = queryOnScreenLineNumberElements(element).sort((a, b) => a.dataset.screenRow - b.dataset.screenRow)
|
||||
const renderedStartRow = component.getRenderedStartRow()
|
||||
const expectedLines = editor.displayLayer.getScreenLines(renderedStartRow, component.getRenderedEndRow())
|
||||
|
||||
expect(renderedLines.length).toBe(expectedLines.length)
|
||||
expect(renderedLineNumbers.length).toBe(expectedLines.length)
|
||||
|
||||
element.remove()
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
describe('randomized tests', () => {
|
||||
let originalTimeout
|
||||
|
||||
@@ -921,7 +961,7 @@ describe('TextEditorComponent', () => {
|
||||
const initialSeed = Date.now()
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let seed = initialSeed + i
|
||||
// seed = 1507224195357
|
||||
// seed = 1507231571985
|
||||
const failureMessage = 'Randomized test failed with seed: ' + seed
|
||||
const random = Random(seed)
|
||||
|
||||
@@ -930,6 +970,7 @@ describe('TextEditorComponent', () => {
|
||||
editor.setSoftWrapped(Boolean(random(2)))
|
||||
await setEditorWidthInCharacters(component, random(20))
|
||||
await setEditorHeightInLines(component, random(10))
|
||||
|
||||
element.focus()
|
||||
|
||||
for (var j = 0; j < 5; j++) {
|
||||
@@ -1368,40 +1409,6 @@ describe('TextEditorComponent', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('always scrolls by a minimum of 1, even when the delta is small or the scroll sensitivity is low', () => {
|
||||
const scrollSensitivity = 10
|
||||
const {component, editor} = buildComponent({height: 50, width: 50, scrollSensitivity})
|
||||
|
||||
{
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -3})
|
||||
expect(component.getScrollTop()).toBe(1)
|
||||
expect(component.getScrollLeft()).toBe(0)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -1px)`)
|
||||
}
|
||||
|
||||
{
|
||||
component.didMouseWheel({wheelDeltaX: -4, wheelDeltaY: 0})
|
||||
expect(component.getScrollTop()).toBe(1)
|
||||
expect(component.getScrollLeft()).toBe(1)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-1px, -1px)`)
|
||||
}
|
||||
|
||||
editor.update({scrollSensitivity: 100})
|
||||
{
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: 0.3})
|
||||
expect(component.getScrollTop()).toBe(0)
|
||||
expect(component.getScrollLeft()).toBe(1)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-1px, 0px)`)
|
||||
}
|
||||
|
||||
{
|
||||
component.didMouseWheel({wheelDeltaX: 0.1, wheelDeltaY: 0})
|
||||
expect(component.getScrollTop()).toBe(0)
|
||||
expect(component.getScrollLeft()).toBe(0)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, 0px)`)
|
||||
}
|
||||
})
|
||||
|
||||
it('inverts deltaX and deltaY when holding shift on Windows and Linux', async () => {
|
||||
const scrollSensitivity = 50
|
||||
const {component, editor} = buildComponent({height: 50, width: 50, scrollSensitivity})
|
||||
|
||||
@@ -6747,6 +6747,14 @@ describe('TextEditor', () => {
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
describe('.scopeDescriptorForBufferPosition(position)', () => {
|
||||
it('returns a default scope descriptor when no language mode is assigned', () => {
|
||||
editor = new TextEditor({buffer: new TextBuffer()})
|
||||
const scopeDescriptor = editor.scopeDescriptorForBufferPosition([0, 0])
|
||||
expect(scopeDescriptor.getScopesArray()).toEqual(['text'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('.shouldPromptToSave()', () => {
|
||||
beforeEach(async () => {
|
||||
editor = await atom.workspace.open('sample.js')
|
||||
|
||||
@@ -170,6 +170,49 @@ describe('TreeSitterLanguageMode', () => {
|
||||
[{text: ')', scopes: []}]
|
||||
])
|
||||
})
|
||||
|
||||
it('handles edits after tokens that end between CR and LF characters (regression)', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
scopes: {
|
||||
'comment': 'comment',
|
||||
'string': 'string',
|
||||
'property_identifier': 'property',
|
||||
}
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
|
||||
buffer.setText([
|
||||
'// abc',
|
||||
'',
|
||||
'a("b").c'
|
||||
].join('\r\n'))
|
||||
|
||||
expectTokensToEqual(editor, [
|
||||
[{text: '// abc', scopes: ['comment']}],
|
||||
[{text: '', scopes: []}],
|
||||
[
|
||||
{text: 'a(', scopes: []},
|
||||
{text: '"b"', scopes: ['string']},
|
||||
{text: ').', scopes: []},
|
||||
{text: 'c', scopes: ['property']}
|
||||
]
|
||||
])
|
||||
|
||||
buffer.insert([2, 0], ' ')
|
||||
expectTokensToEqual(editor, [
|
||||
[{text: '// abc', scopes: ['comment']}],
|
||||
[{text: '', scopes: []}],
|
||||
[
|
||||
{text: ' ', scopes: ['whitespace']},
|
||||
{text: 'a(', scopes: []},
|
||||
{text: '"b"', scopes: ['string']},
|
||||
{text: ').', scopes: []},
|
||||
{text: 'c', scopes: ['property']}
|
||||
]
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('folding', () => {
|
||||
@@ -499,7 +542,7 @@ describe('TreeSitterLanguageMode', () => {
|
||||
buffer.setText('foo({bar: baz});')
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
expect(editor.scopeDescriptorForBufferPosition({row: 0, column: 6}).getScopesArray()).toEqual([
|
||||
expect(editor.scopeDescriptorForBufferPosition([0, 6]).getScopesArray()).toEqual([
|
||||
'javascript',
|
||||
'program',
|
||||
'expression_statement',
|
||||
|
||||
@@ -182,7 +182,7 @@ class ApplicationDelegate {
|
||||
async setUserSettings (config) {
|
||||
this.pendingSettingsUpdateCount++
|
||||
try {
|
||||
await ipcHelpers.call('set-user-settings', config)
|
||||
await ipcHelpers.call('set-user-settings', JSON.stringify(config))
|
||||
} finally {
|
||||
this.pendingSettingsUpdateCount--
|
||||
}
|
||||
@@ -236,7 +236,7 @@ class ApplicationDelegate {
|
||||
return chosen
|
||||
} else {
|
||||
const callback = buttons[buttonLabels[chosen]]
|
||||
if (typeof callback === 'function') callback()
|
||||
if (typeof callback === 'function') return callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ class ApplicationDelegate {
|
||||
this.getCurrentWindow().showSaveDialog(options, callback)
|
||||
} else {
|
||||
// Sync
|
||||
if (typeof params === 'string') {
|
||||
if (typeof options === 'string') {
|
||||
options = {defaultPath: options}
|
||||
}
|
||||
return this.getCurrentWindow().showSaveDialog(options)
|
||||
|
||||
@@ -337,6 +337,14 @@ const configSchema = {
|
||||
value: 'native',
|
||||
description: 'Native operating system APIs'
|
||||
},
|
||||
{
|
||||
value: 'experimental',
|
||||
description: 'Experimental filesystem watching library'
|
||||
},
|
||||
{
|
||||
value: 'poll',
|
||||
description: 'Polling'
|
||||
},
|
||||
{
|
||||
value: 'atom',
|
||||
description: 'Emulated with Atom events'
|
||||
@@ -372,7 +380,7 @@ const configSchema = {
|
||||
// These can be used as globals or scoped, thus defaults.
|
||||
fontFamily: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
default: 'Menlo, Consolas, DejaVu Sans Mono, monospace',
|
||||
description: 'The name of the font family used for editor text.'
|
||||
},
|
||||
fontSize: {
|
||||
|
||||
@@ -432,7 +432,7 @@ class Config {
|
||||
this.settingsLoaded = false
|
||||
this.transactDepth = 0
|
||||
this.pendingOperations = []
|
||||
this.legacyScopeAliases = {}
|
||||
this.legacyScopeAliases = new Map()
|
||||
this.requestSave = _.debounce(() => this.save(), 1)
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ class Config {
|
||||
keyPath,
|
||||
priority.options
|
||||
)
|
||||
legacyScopeDescriptor = this.getLegacyScopeDescriptor(scopeDescriptor)
|
||||
legacyScopeDescriptor = this.getLegacyScopeDescriptorForNewScopeDescriptor(scopeDescriptor)
|
||||
if (legacyScopeDescriptor) {
|
||||
result.push(...Array.from(this.scopedSettingsStore.getAll(
|
||||
legacyScopeDescriptor.getScopeChain(),
|
||||
@@ -872,12 +872,22 @@ class Config {
|
||||
}
|
||||
}
|
||||
|
||||
addLegacyScopeAlias (languageId, legacyScopeName) {
|
||||
this.legacyScopeAliases[languageId] = legacyScopeName
|
||||
getLegacyScopeDescriptorForNewScopeDescriptor (scopeDescriptor) {
|
||||
scopeDescriptor = ScopeDescriptor.fromObject(scopeDescriptor)
|
||||
const legacyAlias = this.legacyScopeAliases.get(scopeDescriptor.scopes[0])
|
||||
if (legacyAlias) {
|
||||
const scopes = scopeDescriptor.scopes.slice()
|
||||
scopes[0] = legacyAlias
|
||||
return new ScopeDescriptor({scopes})
|
||||
}
|
||||
}
|
||||
|
||||
removeLegacyScopeAlias (languageId) {
|
||||
delete this.legacyScopeAliases[languageId]
|
||||
setLegacyScopeAliasForNewScope (languageId, legacyScopeName) {
|
||||
this.legacyScopeAliases.set(languageId, legacyScopeName)
|
||||
}
|
||||
|
||||
removeLegacyScopeAliasForNewScope (languageId) {
|
||||
this.legacyScopeAliases.delete(languageId)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1269,7 +1279,7 @@ class Config {
|
||||
options
|
||||
)
|
||||
|
||||
const legacyScopeDescriptor = this.getLegacyScopeDescriptor(scopeDescriptor)
|
||||
const legacyScopeDescriptor = this.getLegacyScopeDescriptorForNewScopeDescriptor(scopeDescriptor)
|
||||
if (result != null) {
|
||||
return result
|
||||
} else if (legacyScopeDescriptor) {
|
||||
@@ -1297,15 +1307,6 @@ class Config {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getLegacyScopeDescriptor (scopeDescriptor) {
|
||||
const legacyAlias = this.legacyScopeAliases[scopeDescriptor.scopes[0]]
|
||||
if (legacyAlias) {
|
||||
const scopes = scopeDescriptor.scopes.slice()
|
||||
scopes[0] = legacyAlias
|
||||
return new ScopeDescriptor({scopes})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Base schema enforcers. These will coerce raw input into the specified type,
|
||||
|
||||
@@ -5,6 +5,7 @@ fs = require 'fs-plus'
|
||||
{Disposable} = require 'event-kit'
|
||||
{remote} = require 'electron'
|
||||
MenuHelpers = require './menu-helpers'
|
||||
{sortMenuItems} = require './menu-sort-helpers'
|
||||
|
||||
platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
|
||||
|
||||
@@ -149,7 +150,7 @@ class ContextMenuManager
|
||||
@pruneRedundantSeparators(template)
|
||||
@addAccelerators(template)
|
||||
|
||||
template
|
||||
return @sortTemplate(template)
|
||||
|
||||
# Adds an `accelerator` property to items that have key bindings. Electron
|
||||
# uses this property to surface the relevant keymaps in the context menu.
|
||||
@@ -175,6 +176,13 @@ class ContextMenuManager
|
||||
keepNextItemIfSeparator = true
|
||||
index++
|
||||
|
||||
sortTemplate: (template) ->
|
||||
template = sortMenuItems(template)
|
||||
for id, item of template
|
||||
if Array.isArray(item.submenu)
|
||||
item.submenu = @sortTemplate(item.submenu)
|
||||
return template
|
||||
|
||||
# Returns an object compatible with `::add()` or `null`.
|
||||
cloneItemForEvent: (item, event) ->
|
||||
return null if item.devMode and not @devMode
|
||||
|
||||
@@ -135,6 +135,14 @@ class GrammarRegistry {
|
||||
return true
|
||||
}
|
||||
|
||||
// Extended: Get the `languageId` that has been explicitly assigned to
|
||||
// to the given buffer, if any.
|
||||
//
|
||||
// Returns a {String} id of the language
|
||||
getAssignedLanguageId (buffer) {
|
||||
return this.languageOverridesByBufferId.get(buffer.id)
|
||||
}
|
||||
|
||||
// Extended: Remove any language mode override that has been set for the
|
||||
// given {TextBuffer}. This will assign to the buffer the best language
|
||||
// mode available.
|
||||
@@ -293,7 +301,7 @@ class GrammarRegistry {
|
||||
grammarOverrideForPath (filePath) {
|
||||
Grim.deprecate('Use buffer.getLanguageMode().getLanguageId() instead')
|
||||
const buffer = atom.project.findBufferForPath(filePath)
|
||||
if (buffer) return this.languageOverridesByBufferId.get(buffer.id)
|
||||
if (buffer) return this.getAssignedLanguageId(buffer)
|
||||
}
|
||||
|
||||
// Deprecated: Set the grammar override for the given file path.
|
||||
@@ -391,7 +399,7 @@ class GrammarRegistry {
|
||||
if (grammar instanceof TreeSitterGrammar) {
|
||||
this.treeSitterGrammarsById[grammar.id] = grammar
|
||||
if (grammar.legacyScopeName) {
|
||||
this.config.addLegacyScopeAlias(grammar.id, grammar.legacyScopeName)
|
||||
this.config.setLegacyScopeAliasForNewScope(grammar.id, grammar.legacyScopeName)
|
||||
this.textMateScopeNamesByTreeSitterLanguageId.set(grammar.id, grammar.legacyScopeName)
|
||||
this.treeSitterLanguageIdsByTextMateScopeName.set(grammar.legacyScopeName, grammar.id)
|
||||
}
|
||||
@@ -406,7 +414,7 @@ class GrammarRegistry {
|
||||
if (grammar instanceof TreeSitterGrammar) {
|
||||
delete this.treeSitterGrammarsById[grammar.id]
|
||||
if (grammar.legacyScopeName) {
|
||||
this.config.removeLegacyScopeAlias(grammar.id)
|
||||
this.config.removeLegacyScopeAliasForNewScope(grammar.id)
|
||||
this.textMateScopeNamesByTreeSitterLanguageId.delete(grammar.id)
|
||||
this.treeSitterLanguageIdsByTextMateScopeName.delete(grammar.legacyScopeName)
|
||||
}
|
||||
@@ -429,7 +437,7 @@ class GrammarRegistry {
|
||||
this.readGrammar(grammarPath, (error, grammar) => {
|
||||
if (error) return callback(error)
|
||||
this.addGrammar(grammar)
|
||||
callback(grammar)
|
||||
callback(null, grammar)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class AtomApplication extends EventEmitter {
|
||||
// Public: The entry point into the Atom application.
|
||||
static open (options) {
|
||||
if (!options.socketPath) {
|
||||
const username = process.platform === 'win32' ? process.env.USERNAME : process.env.USER
|
||||
const {username} = os.userInfo()
|
||||
|
||||
// Lowercasing the ATOM_HOME to make sure that we don't get multiple sockets
|
||||
// on case-insensitive filesystems due to arbitrary case differences in paths.
|
||||
@@ -44,7 +44,7 @@ class AtomApplication extends EventEmitter {
|
||||
.update('|')
|
||||
.update(process.arch)
|
||||
.update('|')
|
||||
.update(username)
|
||||
.update(username || '')
|
||||
.update('|')
|
||||
.update(atomHomeUnique)
|
||||
|
||||
@@ -116,7 +116,9 @@ class AtomApplication extends EventEmitter {
|
||||
|
||||
this.configFile = new ConfigFile(configFilePath)
|
||||
this.config = new Config({
|
||||
saveCallback: settings => this.configFile.update(settings)
|
||||
saveCallback: settings => {
|
||||
if (!this.quitting) return this.configFile.update(settings)
|
||||
}
|
||||
})
|
||||
this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)})
|
||||
|
||||
@@ -146,8 +148,6 @@ class AtomApplication extends EventEmitter {
|
||||
this.config.set('core.titleBar', 'custom')
|
||||
}
|
||||
|
||||
this.config.onDidChange('core.titleBar', this.promptForRestart.bind(this))
|
||||
|
||||
process.nextTick(() => this.autoUpdateManager.initialize())
|
||||
this.applicationMenu = new ApplicationMenu(this.version, this.autoUpdateManager)
|
||||
this.atomProtocolHandler = new AtomProtocolHandler(this.resourcePath, this.safeMode)
|
||||
@@ -171,6 +171,7 @@ class AtomApplication extends EventEmitter {
|
||||
if (!this.configFilePromise) {
|
||||
this.configFilePromise = this.configFile.watch()
|
||||
this.disposable.add(await this.configFilePromise)
|
||||
this.config.onDidChange('core.titleBar', this.promptForRestart.bind(this))
|
||||
}
|
||||
|
||||
const optionsForWindowsToOpen = []
|
||||
@@ -562,7 +563,7 @@ class AtomApplication extends EventEmitter {
|
||||
}))
|
||||
|
||||
this.disposable.add(ipcHelpers.respondTo('set-user-settings', (window, settings) =>
|
||||
this.configFile.update(settings)
|
||||
this.configFile.update(JSON.parse(settings))
|
||||
))
|
||||
|
||||
this.disposable.add(ipcHelpers.respondTo('center-window', window => window.center()))
|
||||
@@ -842,13 +843,12 @@ class AtomApplication extends EventEmitter {
|
||||
let existingWindow
|
||||
if (!newWindow) {
|
||||
existingWindow = this.windowForPaths(pathsToOpen, devMode)
|
||||
const stats = pathsToOpen.map(pathToOpen => fs.statSyncNoException(pathToOpen))
|
||||
if (!existingWindow) {
|
||||
let lastWindow = window || this.getLastFocusedWindow()
|
||||
if (lastWindow && lastWindow.devMode === devMode) {
|
||||
if (addToLastWindow || (
|
||||
stats.every(s => s.isFile && s.isFile()) ||
|
||||
(stats.some(s => s.isDirectory && s.isDirectory()) && !lastWindow.hasProjectPath()))) {
|
||||
locationsToOpen.every(({stat}) => stat && stat.isFile()) ||
|
||||
(locationsToOpen.some(({stat}) => stat && stat.isDirectory()) && !lastWindow.hasProjectPath()))) {
|
||||
existingWindow = lastWindow
|
||||
}
|
||||
}
|
||||
@@ -1273,11 +1273,11 @@ class AtomApplication extends EventEmitter {
|
||||
initialLine = initialColumn = null
|
||||
}
|
||||
|
||||
if (url.parse(pathToOpen).protocol == null) {
|
||||
pathToOpen = path.resolve(executedFrom, fs.normalize(pathToOpen))
|
||||
}
|
||||
const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(pathToOpen)))
|
||||
const stat = fs.statSyncNoException(normalizedPath)
|
||||
if (stat || !url.parse(pathToOpen).protocol) pathToOpen = normalizedPath
|
||||
|
||||
return {pathToOpen, initialLine, initialColumn}
|
||||
return {pathToOpen, stat, initialLine, initialColumn}
|
||||
}
|
||||
|
||||
// Opens a native dialog to prompt the user for a path.
|
||||
|
||||
@@ -61,7 +61,6 @@ class AtomWindow extends EventEmitter {
|
||||
}
|
||||
|
||||
this.loadDataOverProcessBoundary()
|
||||
|
||||
this.handleEvents()
|
||||
|
||||
this.loadSettings = Object.assign({}, settings)
|
||||
@@ -74,14 +73,13 @@ class AtomWindow extends EventEmitter {
|
||||
|
||||
if (!this.loadSettings.initialPaths) {
|
||||
this.loadSettings.initialPaths = []
|
||||
for (const {pathToOpen} of locationsToOpen) {
|
||||
for (const {pathToOpen, stat} of locationsToOpen) {
|
||||
if (!pathToOpen) continue
|
||||
const stat = fs.statSyncNoException(pathToOpen) || null
|
||||
if (stat && stat.isDirectory()) {
|
||||
this.loadSettings.initialPaths.push(pathToOpen)
|
||||
} else {
|
||||
const parentDirectory = path.dirname(pathToOpen)
|
||||
if ((stat && stat.isFile()) || fs.existsSync(parentDirectory)) {
|
||||
if (stat && stat.isFile() || fs.existsSync(parentDirectory)) {
|
||||
this.loadSettings.initialPaths.push(parentDirectory)
|
||||
} else {
|
||||
this.loadSettings.initialPaths.push(pathToOpen)
|
||||
@@ -165,12 +163,13 @@ class AtomWindow extends EventEmitter {
|
||||
|
||||
containsPath (pathToCheck) {
|
||||
if (!pathToCheck) return false
|
||||
const stat = fs.statSyncNoException(pathToCheck)
|
||||
if (stat && stat.isDirectory()) return false
|
||||
|
||||
return this.representedDirectoryPaths.some(projectPath =>
|
||||
pathToCheck === projectPath || pathToCheck.startsWith(path.join(projectPath, path.sep))
|
||||
)
|
||||
let stat
|
||||
return this.representedDirectoryPaths.some(projectPath => {
|
||||
if (pathToCheck === projectPath) return true
|
||||
if (!pathToCheck.startsWith(path.join(projectPath, path.sep))) return false
|
||||
if (stat === undefined) stat = fs.statSyncNoException(pathToCheck)
|
||||
return !stat || !stat.isDirectory()
|
||||
})
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
|
||||
@@ -83,7 +83,11 @@ function cloneMenuItem (item) {
|
||||
'submenu',
|
||||
'commandDetail',
|
||||
'role',
|
||||
'accelerator'
|
||||
'accelerator',
|
||||
'before',
|
||||
'after',
|
||||
'beforeGroupContaining',
|
||||
'afterGroupContaining'
|
||||
)
|
||||
if (item.submenu != null) {
|
||||
item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem))
|
||||
|
||||
186
src/menu-sort-helpers.js
Normal file
186
src/menu-sort-helpers.js
Normal file
@@ -0,0 +1,186 @@
|
||||
// UTILS
|
||||
|
||||
function splitArray (arr, predicate) {
|
||||
let lastArr = []
|
||||
const multiArr = [lastArr]
|
||||
arr.forEach(item => {
|
||||
if (predicate(item)) {
|
||||
if (lastArr.length > 0) {
|
||||
lastArr = []
|
||||
multiArr.push(lastArr)
|
||||
}
|
||||
} else {
|
||||
lastArr.push(item)
|
||||
}
|
||||
})
|
||||
return multiArr
|
||||
}
|
||||
|
||||
function joinArrays (arrays, joiner) {
|
||||
const joinedArr = []
|
||||
arrays.forEach((arr, i) => {
|
||||
if (i > 0 && arr.length > 0) {
|
||||
joinedArr.push(joiner)
|
||||
}
|
||||
joinedArr.push(...arr)
|
||||
})
|
||||
return joinedArr
|
||||
}
|
||||
|
||||
const pushOntoMultiMap = (map, key, value) => {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, [])
|
||||
}
|
||||
map.get(key).push(value)
|
||||
}
|
||||
|
||||
function indexOfGroupContainingCommand (groups, command, ignoreGroup) {
|
||||
return groups.findIndex(
|
||||
candiateGroup =>
|
||||
candiateGroup !== ignoreGroup &&
|
||||
candiateGroup.some(
|
||||
candidateItem => candidateItem.command === command
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Sort nodes topologically using a depth-first approach. Encountered cycles
|
||||
// are broken.
|
||||
function sortTopologically (originalOrder, edgesById) {
|
||||
const sorted = []
|
||||
const marked = new Set()
|
||||
|
||||
function visit (id) {
|
||||
if (marked.has(id)) {
|
||||
// Either this node has already been placed, or we have encountered a
|
||||
// cycle and need to exit.
|
||||
return
|
||||
}
|
||||
marked.add(id)
|
||||
const edges = edgesById.get(id)
|
||||
if (edges != null) {
|
||||
edges.forEach(visit)
|
||||
}
|
||||
sorted.push(id)
|
||||
}
|
||||
|
||||
originalOrder.forEach(visit)
|
||||
return sorted
|
||||
}
|
||||
|
||||
function attemptToMergeAGroup (groups) {
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const group = groups[i]
|
||||
for (const item of group) {
|
||||
const toCommands = [...(item.before || []), ...(item.after || [])]
|
||||
for (const command of toCommands) {
|
||||
const index = indexOfGroupContainingCommand(groups, command, group)
|
||||
if (index === -1) {
|
||||
// No valid edge for this command
|
||||
continue
|
||||
}
|
||||
const mergeTarget = groups[index]
|
||||
// Merge with group containing `command`
|
||||
mergeTarget.push(...group)
|
||||
groups.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Merge groups based on before/after positions
|
||||
// Mutates both the array of groups, and the individual group arrays.
|
||||
function mergeGroups (groups) {
|
||||
let mergedAGroup = true
|
||||
while (mergedAGroup) {
|
||||
mergedAGroup = attemptToMergeAGroup(groups)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
function sortItemsInGroup (group) {
|
||||
const originalOrder = group.map((node, i) => i)
|
||||
const edges = new Map()
|
||||
const commandToIndex = new Map(group.map((item, i) => [item.command, i]))
|
||||
|
||||
group.forEach((item, i) => {
|
||||
if (item.before) {
|
||||
item.before.forEach(toCommand => {
|
||||
const to = commandToIndex.get(toCommand)
|
||||
if (to != null) {
|
||||
pushOntoMultiMap(edges, to, i)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (item.after) {
|
||||
item.after.forEach(toCommand => {
|
||||
const to = commandToIndex.get(toCommand)
|
||||
if (to != null) {
|
||||
pushOntoMultiMap(edges, i, to)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const sortedNodes = sortTopologically(originalOrder, edges)
|
||||
|
||||
return sortedNodes.map(i => group[i])
|
||||
}
|
||||
|
||||
function findEdgesInGroup (groups, i, edges) {
|
||||
const group = groups[i]
|
||||
for (const item of group) {
|
||||
if (item.beforeGroupContaining) {
|
||||
for (const command of item.beforeGroupContaining) {
|
||||
const to = indexOfGroupContainingCommand(groups, command, group)
|
||||
if (to !== -1) {
|
||||
pushOntoMultiMap(edges, to, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.afterGroupContaining) {
|
||||
for (const command of item.afterGroupContaining) {
|
||||
const to = indexOfGroupContainingCommand(groups, command, group)
|
||||
if (to !== -1) {
|
||||
pushOntoMultiMap(edges, i, to)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortGroups (groups) {
|
||||
const originalOrder = groups.map((item, i) => i)
|
||||
const edges = new Map()
|
||||
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
findEdgesInGroup(groups, i, edges)
|
||||
}
|
||||
|
||||
const sortedGroupIndexes = sortTopologically(originalOrder, edges)
|
||||
return sortedGroupIndexes.map(i => groups[i])
|
||||
}
|
||||
|
||||
function isSeparator (item) {
|
||||
return item.type === 'separator'
|
||||
}
|
||||
|
||||
function sortMenuItems (menuItems) {
|
||||
// Split the items into their implicit groups based upon separators.
|
||||
const groups = splitArray(menuItems, isSeparator)
|
||||
// Merge groups that contain before/after references to eachother.
|
||||
const mergedGroups = mergeGroups(groups)
|
||||
// Sort each individual group internally.
|
||||
const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup)
|
||||
// Sort the groups based upon their beforeGroupContaining/afterGroupContaining
|
||||
// references.
|
||||
const sortedGroups = sortGroups(mergedGroupsWithSortedItems)
|
||||
// Join the groups back
|
||||
return joinArrays(sortedGroups, { type: 'separator' })
|
||||
}
|
||||
|
||||
module.exports = {sortMenuItems}
|
||||
@@ -43,8 +43,8 @@ class Package {
|
||||
? params.bundledPackage
|
||||
: this.packageManager.isBundledPackagePath(this.path)
|
||||
this.name =
|
||||
params.name ||
|
||||
(this.metadata && this.metadata.name) ||
|
||||
params.name ||
|
||||
path.basename(this.path)
|
||||
this.reset()
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
{CompositeDisposable} = require 'event-kit'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
class PaneContainerElement extends HTMLElement
|
||||
createdCallback: ->
|
||||
@subscriptions = new CompositeDisposable
|
||||
@classList.add 'panes'
|
||||
|
||||
initialize: (@model, {@views}) ->
|
||||
throw new Error("Must pass a views parameter when initializing PaneContainerElements") unless @views?
|
||||
|
||||
@subscriptions.add @model.observeRoot(@rootChanged.bind(this))
|
||||
this
|
||||
|
||||
rootChanged: (root) ->
|
||||
focusedElement = document.activeElement if @hasFocus()
|
||||
@firstChild?.remove()
|
||||
if root?
|
||||
view = @views.getView(root)
|
||||
@appendChild(view)
|
||||
focusedElement?.focus()
|
||||
|
||||
hasFocus: ->
|
||||
this is document.activeElement or @contains(document.activeElement)
|
||||
|
||||
|
||||
module.exports = PaneContainerElement = document.registerElement 'atom-pane-container', prototype: PaneContainerElement.prototype
|
||||
40
src/pane-container-element.js
Normal file
40
src/pane-container-element.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const {CompositeDisposable} = require('event-kit')
|
||||
|
||||
class PaneContainerElement extends HTMLElement {
|
||||
createdCallback () {
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.classList.add('panes')
|
||||
}
|
||||
|
||||
initialize (model, {views}) {
|
||||
this.model = model
|
||||
this.views = views
|
||||
if (this.views == null) {
|
||||
throw new Error('Must pass a views parameter when initializing PaneContainerElements')
|
||||
}
|
||||
this.subscriptions.add(this.model.observeRoot(this.rootChanged.bind(this)))
|
||||
return this
|
||||
}
|
||||
|
||||
rootChanged (root) {
|
||||
const focusedElement = this.hasFocus() ? document.activeElement : null
|
||||
if (this.firstChild != null) {
|
||||
this.firstChild.remove()
|
||||
}
|
||||
if (root != null) {
|
||||
const view = this.views.getView(root)
|
||||
this.appendChild(view)
|
||||
if (focusedElement != null) {
|
||||
focusedElement.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasFocus () {
|
||||
return this === document.activeElement || this.contains(document.activeElement)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = document.registerElement('atom-pane-container', {
|
||||
prototype: PaneContainerElement.prototype
|
||||
})
|
||||
@@ -1,139 +0,0 @@
|
||||
path = require 'path'
|
||||
{CompositeDisposable} = require 'event-kit'
|
||||
|
||||
class PaneElement extends HTMLElement
|
||||
attached: false
|
||||
|
||||
createdCallback: ->
|
||||
@attached = false
|
||||
@subscriptions = new CompositeDisposable
|
||||
@inlineDisplayStyles = new WeakMap
|
||||
|
||||
@initializeContent()
|
||||
@subscribeToDOMEvents()
|
||||
|
||||
attachedCallback: ->
|
||||
@attached = true
|
||||
@focus() if @model.isFocused()
|
||||
|
||||
detachedCallback: ->
|
||||
@attached = false
|
||||
|
||||
initializeContent: ->
|
||||
@setAttribute 'class', 'pane'
|
||||
@setAttribute 'tabindex', -1
|
||||
@appendChild @itemViews = document.createElement('div')
|
||||
@itemViews.setAttribute 'class', 'item-views'
|
||||
|
||||
subscribeToDOMEvents: ->
|
||||
handleFocus = (event) =>
|
||||
@model.focus() unless @isActivating or @model.isDestroyed() or @contains(event.relatedTarget)
|
||||
if event.target is this and view = @getActiveView()
|
||||
view.focus()
|
||||
event.stopPropagation()
|
||||
|
||||
handleBlur = (event) =>
|
||||
@model.blur() unless @contains(event.relatedTarget)
|
||||
|
||||
handleDragOver = (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
handleDrop = (event) =>
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
@getModel().activate()
|
||||
pathsToOpen = Array::map.call event.dataTransfer.files, (file) -> file.path
|
||||
@applicationDelegate.open({pathsToOpen}) if pathsToOpen.length > 0
|
||||
|
||||
@addEventListener 'focus', handleFocus, true
|
||||
@addEventListener 'blur', handleBlur, true
|
||||
@addEventListener 'dragover', handleDragOver
|
||||
@addEventListener 'drop', handleDrop
|
||||
|
||||
initialize: (@model, {@views, @applicationDelegate}) ->
|
||||
throw new Error("Must pass a views parameter when initializing PaneElements") unless @views?
|
||||
throw new Error("Must pass an applicationDelegate parameter when initializing PaneElements") unless @applicationDelegate?
|
||||
|
||||
@subscriptions.add @model.onDidActivate(@activated.bind(this))
|
||||
@subscriptions.add @model.observeActive(@activeStatusChanged.bind(this))
|
||||
@subscriptions.add @model.observeActiveItem(@activeItemChanged.bind(this))
|
||||
@subscriptions.add @model.onDidRemoveItem(@itemRemoved.bind(this))
|
||||
@subscriptions.add @model.onDidDestroy(@paneDestroyed.bind(this))
|
||||
@subscriptions.add @model.observeFlexScale(@flexScaleChanged.bind(this))
|
||||
this
|
||||
|
||||
getModel: -> @model
|
||||
|
||||
activated: ->
|
||||
@isActivating = true
|
||||
@focus() unless @hasFocus() # Don't steal focus from children.
|
||||
@isActivating = false
|
||||
|
||||
activeStatusChanged: (active) ->
|
||||
if active
|
||||
@classList.add('active')
|
||||
else
|
||||
@classList.remove('active')
|
||||
|
||||
activeItemChanged: (item) ->
|
||||
delete @dataset.activeItemName
|
||||
delete @dataset.activeItemPath
|
||||
@changePathDisposable?.dispose()
|
||||
|
||||
return unless item?
|
||||
|
||||
hasFocus = @hasFocus()
|
||||
itemView = @views.getView(item)
|
||||
|
||||
if itemPath = item.getPath?()
|
||||
@dataset.activeItemName = path.basename(itemPath)
|
||||
@dataset.activeItemPath = itemPath
|
||||
|
||||
if item.onDidChangePath?
|
||||
@changePathDisposable = item.onDidChangePath =>
|
||||
itemPath = item.getPath()
|
||||
@dataset.activeItemName = path.basename(itemPath)
|
||||
@dataset.activeItemPath = itemPath
|
||||
|
||||
unless @itemViews.contains(itemView)
|
||||
@itemViews.appendChild(itemView)
|
||||
|
||||
for child in @itemViews.children
|
||||
if child is itemView
|
||||
@showItemView(child) if @attached
|
||||
else
|
||||
@hideItemView(child)
|
||||
|
||||
itemView.focus() if hasFocus
|
||||
|
||||
showItemView: (itemView) ->
|
||||
inlineDisplayStyle = @inlineDisplayStyles.get(itemView)
|
||||
if inlineDisplayStyle?
|
||||
itemView.style.display = inlineDisplayStyle
|
||||
else
|
||||
itemView.style.display = ''
|
||||
|
||||
hideItemView: (itemView) ->
|
||||
inlineDisplayStyle = itemView.style.display
|
||||
unless inlineDisplayStyle is 'none'
|
||||
@inlineDisplayStyles.set(itemView, inlineDisplayStyle) if inlineDisplayStyle?
|
||||
itemView.style.display = 'none'
|
||||
|
||||
itemRemoved: ({item, index, destroyed}) ->
|
||||
if viewToRemove = @views.getView(item)
|
||||
viewToRemove.remove()
|
||||
|
||||
paneDestroyed: ->
|
||||
@subscriptions.dispose()
|
||||
@changePathDisposable?.dispose()
|
||||
|
||||
flexScaleChanged: (flexScale) ->
|
||||
@style.flexGrow = flexScale
|
||||
|
||||
getActiveView: -> @views.getView(@model.getActiveItem())
|
||||
|
||||
hasFocus: ->
|
||||
this is document.activeElement or @contains(document.activeElement)
|
||||
|
||||
module.exports = PaneElement = document.registerElement 'atom-pane', prototype: PaneElement.prototype
|
||||
218
src/pane-element.js
Normal file
218
src/pane-element.js
Normal file
@@ -0,0 +1,218 @@
|
||||
const path = require('path')
|
||||
const {CompositeDisposable} = require('event-kit')
|
||||
|
||||
class PaneElement extends HTMLElement {
|
||||
createdCallback () {
|
||||
this.attached = false
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.inlineDisplayStyles = new WeakMap()
|
||||
this.initializeContent()
|
||||
this.subscribeToDOMEvents()
|
||||
}
|
||||
|
||||
attachedCallback () {
|
||||
this.attached = true
|
||||
if (this.model.isFocused()) {
|
||||
this.focus()
|
||||
}
|
||||
}
|
||||
|
||||
detachedCallback () {
|
||||
this.attached = false
|
||||
}
|
||||
|
||||
initializeContent () {
|
||||
this.setAttribute('class', 'pane')
|
||||
this.setAttribute('tabindex', -1)
|
||||
this.itemViews = document.createElement('div')
|
||||
this.appendChild(this.itemViews)
|
||||
this.itemViews.setAttribute('class', 'item-views')
|
||||
}
|
||||
|
||||
subscribeToDOMEvents () {
|
||||
const handleFocus = event => {
|
||||
if (
|
||||
!(
|
||||
this.isActivating ||
|
||||
this.model.isDestroyed() ||
|
||||
this.contains(event.relatedTarget)
|
||||
)
|
||||
) {
|
||||
this.model.focus()
|
||||
}
|
||||
if (event.target !== this) return
|
||||
const view = this.getActiveView()
|
||||
if (view) {
|
||||
view.focus()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
const handleBlur = event => {
|
||||
if (!this.contains(event.relatedTarget)) {
|
||||
this.model.blur()
|
||||
}
|
||||
}
|
||||
const handleDragOver = event => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
const handleDrop = event => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.getModel().activate()
|
||||
const pathsToOpen = [...event.dataTransfer.files].map(file => file.path)
|
||||
if (pathsToOpen.length > 0) {
|
||||
this.applicationDelegate.open({pathsToOpen})
|
||||
}
|
||||
}
|
||||
this.addEventListener('focus', handleFocus, true)
|
||||
this.addEventListener('blur', handleBlur, true)
|
||||
this.addEventListener('dragover', handleDragOver)
|
||||
this.addEventListener('drop', handleDrop)
|
||||
}
|
||||
|
||||
initialize (model, {views, applicationDelegate}) {
|
||||
this.model = model
|
||||
this.views = views
|
||||
this.applicationDelegate = applicationDelegate
|
||||
if (this.views == null) {
|
||||
throw new Error(
|
||||
'Must pass a views parameter when initializing PaneElements'
|
||||
)
|
||||
}
|
||||
if (this.applicationDelegate == null) {
|
||||
throw new Error(
|
||||
'Must pass an applicationDelegate parameter when initializing PaneElements'
|
||||
)
|
||||
}
|
||||
this.subscriptions.add(this.model.onDidActivate(this.activated.bind(this)))
|
||||
this.subscriptions.add(
|
||||
this.model.observeActive(this.activeStatusChanged.bind(this))
|
||||
)
|
||||
this.subscriptions.add(
|
||||
this.model.observeActiveItem(this.activeItemChanged.bind(this))
|
||||
)
|
||||
this.subscriptions.add(
|
||||
this.model.onDidRemoveItem(this.itemRemoved.bind(this))
|
||||
)
|
||||
this.subscriptions.add(
|
||||
this.model.onDidDestroy(this.paneDestroyed.bind(this))
|
||||
)
|
||||
this.subscriptions.add(
|
||||
this.model.observeFlexScale(this.flexScaleChanged.bind(this))
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
getModel () {
|
||||
return this.model
|
||||
}
|
||||
|
||||
activated () {
|
||||
this.isActivating = true
|
||||
if (!this.hasFocus()) {
|
||||
// Don't steal focus from children.
|
||||
this.focus()
|
||||
}
|
||||
this.isActivating = false
|
||||
}
|
||||
|
||||
activeStatusChanged (active) {
|
||||
if (active) {
|
||||
this.classList.add('active')
|
||||
} else {
|
||||
this.classList.remove('active')
|
||||
}
|
||||
}
|
||||
|
||||
activeItemChanged (item) {
|
||||
delete this.dataset.activeItemName
|
||||
delete this.dataset.activeItemPath
|
||||
if (this.changePathDisposable != null) {
|
||||
this.changePathDisposable.dispose()
|
||||
}
|
||||
if (item == null) {
|
||||
return
|
||||
}
|
||||
const hasFocus = this.hasFocus()
|
||||
const itemView = this.views.getView(item)
|
||||
const itemPath = typeof item.getPath === 'function' ? item.getPath() : null
|
||||
if (itemPath) {
|
||||
this.dataset.activeItemName = path.basename(itemPath)
|
||||
this.dataset.activeItemPath = itemPath
|
||||
if (item.onDidChangePath != null) {
|
||||
this.changePathDisposable = item.onDidChangePath(() => {
|
||||
const itemPath = item.getPath()
|
||||
this.dataset.activeItemName = path.basename(itemPath)
|
||||
this.dataset.activeItemPath = itemPath
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!this.itemViews.contains(itemView)) {
|
||||
this.itemViews.appendChild(itemView)
|
||||
}
|
||||
for (const child of this.itemViews.children) {
|
||||
if (child === itemView) {
|
||||
if (this.attached) {
|
||||
this.showItemView(child)
|
||||
}
|
||||
} else {
|
||||
this.hideItemView(child)
|
||||
}
|
||||
}
|
||||
if (hasFocus) {
|
||||
itemView.focus()
|
||||
}
|
||||
}
|
||||
|
||||
showItemView (itemView) {
|
||||
const inlineDisplayStyle = this.inlineDisplayStyles.get(itemView)
|
||||
if (inlineDisplayStyle != null) {
|
||||
itemView.style.display = inlineDisplayStyle
|
||||
} else {
|
||||
itemView.style.display = ''
|
||||
}
|
||||
}
|
||||
|
||||
hideItemView (itemView) {
|
||||
const inlineDisplayStyle = itemView.style.display
|
||||
if (inlineDisplayStyle !== 'none') {
|
||||
if (inlineDisplayStyle != null) {
|
||||
this.inlineDisplayStyles.set(itemView, inlineDisplayStyle)
|
||||
}
|
||||
itemView.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
itemRemoved ({item, index, destroyed}) {
|
||||
const viewToRemove = this.views.getView(item)
|
||||
if (viewToRemove) {
|
||||
viewToRemove.remove()
|
||||
}
|
||||
}
|
||||
|
||||
paneDestroyed () {
|
||||
this.subscriptions.dispose()
|
||||
if (this.changePathDisposable != null) {
|
||||
this.changePathDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
flexScaleChanged (flexScale) {
|
||||
this.style.flexGrow = flexScale
|
||||
}
|
||||
|
||||
getActiveView () {
|
||||
return this.views.getView(this.model.getActiveItem())
|
||||
}
|
||||
|
||||
hasFocus () {
|
||||
return (
|
||||
this === document.activeElement || this.contains(document.activeElement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = document.registerElement('atom-pane', {
|
||||
prototype: PaneElement.prototype
|
||||
})
|
||||
@@ -3,6 +3,7 @@ const path = require('path')
|
||||
|
||||
const {Emitter, Disposable, CompositeDisposable} = require('event-kit')
|
||||
const nsfw = require('@atom/nsfw')
|
||||
const watcher = require('@atom/watcher')
|
||||
const {NativeWatcherRegistry} = require('./native-watcher-registry')
|
||||
|
||||
// Private: Associate native watcher action flags with descriptive String equivalents.
|
||||
@@ -21,145 +22,7 @@ const WATCHER_STATE = {
|
||||
STOPPING: Symbol('stopping')
|
||||
}
|
||||
|
||||
// Private: Emulate a "filesystem watcher" by subscribing to Atom events like buffers being saved. This will miss
|
||||
// any changes made to files outside of Atom, but it also has no overhead.
|
||||
class AtomBackend {
|
||||
async start (rootPath, eventCallback, errorCallback) {
|
||||
const getRealPath = givenPath => {
|
||||
return new Promise(resolve => {
|
||||
fs.realpath(givenPath, (err, resolvedPath) => {
|
||||
err ? resolve(null) : resolve(resolvedPath)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.subs = new CompositeDisposable()
|
||||
|
||||
this.subs.add(atom.workspace.observeTextEditors(async editor => {
|
||||
let realPath = await getRealPath(editor.getPath())
|
||||
if (!realPath || !realPath.startsWith(rootPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
const announce = (action, oldPath) => {
|
||||
const payload = {action, path: realPath}
|
||||
if (oldPath) payload.oldPath = oldPath
|
||||
eventCallback([payload])
|
||||
}
|
||||
|
||||
const buffer = editor.getBuffer()
|
||||
|
||||
this.subs.add(buffer.onDidConflict(() => announce('modified')))
|
||||
this.subs.add(buffer.onDidReload(() => announce('modified')))
|
||||
this.subs.add(buffer.onDidSave(event => {
|
||||
if (event.path === realPath) {
|
||||
announce('modified')
|
||||
} else {
|
||||
const oldPath = realPath
|
||||
realPath = event.path
|
||||
announce('renamed', oldPath)
|
||||
}
|
||||
}))
|
||||
|
||||
this.subs.add(buffer.onDidDelete(() => announce('deleted')))
|
||||
|
||||
this.subs.add(buffer.onDidChangePath(newPath => {
|
||||
if (newPath !== realPath) {
|
||||
const oldPath = realPath
|
||||
realPath = newPath
|
||||
announce('renamed', oldPath)
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
// Giant-ass brittle hack to hook files (and eventually directories) created from the TreeView.
|
||||
const treeViewPackage = await atom.packages.getLoadedPackage('tree-view')
|
||||
if (!treeViewPackage) return
|
||||
await treeViewPackage.activationPromise
|
||||
const treeViewModule = treeViewPackage.mainModule
|
||||
if (!treeViewModule) return
|
||||
const treeView = treeViewModule.getTreeViewInstance()
|
||||
|
||||
const isOpenInEditor = async eventPath => {
|
||||
const openPaths = await Promise.all(
|
||||
atom.workspace.getTextEditors().map(editor => getRealPath(editor.getPath()))
|
||||
)
|
||||
return openPaths.includes(eventPath)
|
||||
}
|
||||
|
||||
this.subs.add(treeView.onFileCreated(async event => {
|
||||
const realPath = await getRealPath(event.path)
|
||||
if (!realPath) return
|
||||
|
||||
eventCallback([{action: 'added', path: realPath}])
|
||||
}))
|
||||
|
||||
this.subs.add(treeView.onEntryDeleted(async event => {
|
||||
const realPath = await getRealPath(event.path)
|
||||
if (!realPath || isOpenInEditor(realPath)) return
|
||||
|
||||
eventCallback([{action: 'deleted', path: realPath}])
|
||||
}))
|
||||
|
||||
this.subs.add(treeView.onEntryMoved(async event => {
|
||||
const [realNewPath, realOldPath] = await Promise.all([
|
||||
getRealPath(event.newPath),
|
||||
getRealPath(event.initialPath)
|
||||
])
|
||||
if (!realNewPath || !realOldPath || isOpenInEditor(realNewPath) || isOpenInEditor(realOldPath)) return
|
||||
|
||||
eventCallback([{action: 'renamed', path: realNewPath, oldPath: realOldPath}])
|
||||
}))
|
||||
}
|
||||
|
||||
async stop () {
|
||||
this.subs && this.subs.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Implement a native watcher by translating events from an NSFW watcher.
|
||||
class NSFWBackend {
|
||||
async start (rootPath, eventCallback, errorCallback) {
|
||||
const handler = events => {
|
||||
eventCallback(events.map(event => {
|
||||
const action = ACTION_MAP.get(event.action) || `unexpected (${event.action})`
|
||||
const payload = {action}
|
||||
|
||||
if (event.file) {
|
||||
payload.path = path.join(event.directory, event.file)
|
||||
} else {
|
||||
payload.oldPath = path.join(event.directory, event.oldFile)
|
||||
payload.path = path.join(event.directory, event.newFile)
|
||||
}
|
||||
|
||||
return payload
|
||||
}))
|
||||
}
|
||||
|
||||
this.watcher = await nsfw(
|
||||
rootPath,
|
||||
handler,
|
||||
{debounceMS: 100, errorCallback}
|
||||
)
|
||||
|
||||
await this.watcher.start()
|
||||
}
|
||||
|
||||
stop () {
|
||||
return this.watcher.stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Map configuration settings from the feature flag to backend implementations.
|
||||
const BACKENDS = {
|
||||
atom: AtomBackend,
|
||||
native: NSFWBackend
|
||||
}
|
||||
|
||||
// Private: the backend implementation to fall back to if the config setting is invalid.
|
||||
const DEFAULT_BACKEND = BACKENDS.nsfw
|
||||
|
||||
// Private: Interface with and normalize events from a native OS filesystem watcher.
|
||||
// Private: Interface with and normalize events from a filesystem watcher implementation.
|
||||
class NativeWatcher {
|
||||
|
||||
// Private: Initialize a native watcher on a path.
|
||||
@@ -170,37 +33,10 @@ class NativeWatcher {
|
||||
this.emitter = new Emitter()
|
||||
this.subs = new CompositeDisposable()
|
||||
|
||||
this.backend = null
|
||||
this.state = WATCHER_STATE.STOPPED
|
||||
|
||||
this.onEvents = this.onEvents.bind(this)
|
||||
this.onError = this.onError.bind(this)
|
||||
|
||||
this.subs.add(atom.config.onDidChange('core.fileSystemWatcher', async () => {
|
||||
if (this.state === WATCHER_STATE.STARTING) {
|
||||
// Wait for this watcher to finish starting.
|
||||
await new Promise(resolve => {
|
||||
const sub = this.onDidStart(() => {
|
||||
sub.dispose()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Re-read the config setting in case it's changed again while we were waiting for the watcher
|
||||
// to start.
|
||||
const Backend = this.getCurrentBackend()
|
||||
if (this.state === WATCHER_STATE.RUNNING && !(this.backend instanceof Backend)) {
|
||||
await this.stop()
|
||||
await this.start()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// Private: Read the `core.fileSystemWatcher` setting to determine the filesystem backend to use.
|
||||
getCurrentBackend () {
|
||||
const setting = atom.config.get('core.fileSystemWatcher')
|
||||
return BACKENDS[setting] || DEFAULT_BACKEND
|
||||
}
|
||||
|
||||
// Private: Begin watching for filesystem events.
|
||||
@@ -212,15 +48,16 @@ class NativeWatcher {
|
||||
}
|
||||
this.state = WATCHER_STATE.STARTING
|
||||
|
||||
const Backend = this.getCurrentBackend()
|
||||
|
||||
this.backend = new Backend()
|
||||
await this.backend.start(this.normalizedPath, this.onEvents, this.onError)
|
||||
await this.doStart()
|
||||
|
||||
this.state = WATCHER_STATE.RUNNING
|
||||
this.emitter.emit('did-start')
|
||||
}
|
||||
|
||||
doStart () {
|
||||
return Promise.reject('doStart() not overridden')
|
||||
}
|
||||
|
||||
// Private: Return true if the underlying watcher is actively listening for filesystem events.
|
||||
isRunning () {
|
||||
return this.state === WATCHER_STATE.RUNNING
|
||||
@@ -283,8 +120,8 @@ class NativeWatcher {
|
||||
//
|
||||
// * `replacement` the new {NativeWatcher} instance that a live {Watcher} instance should reattach to instead.
|
||||
// * `watchedPath` absolute path watched by the new {NativeWatcher}.
|
||||
reattachTo (replacement, watchedPath) {
|
||||
this.emitter.emit('should-detach', {replacement, watchedPath})
|
||||
reattachTo (replacement, watchedPath, options) {
|
||||
this.emitter.emit('should-detach', {replacement, watchedPath, options})
|
||||
}
|
||||
|
||||
// Private: Stop the native watcher and release any operating system resources associated with it.
|
||||
@@ -297,12 +134,17 @@ class NativeWatcher {
|
||||
this.state = WATCHER_STATE.STOPPING
|
||||
this.emitter.emit('will-stop')
|
||||
|
||||
await this.backend.stop()
|
||||
await this.doStop()
|
||||
|
||||
this.state = WATCHER_STATE.STOPPED
|
||||
|
||||
this.emitter.emit('did-stop')
|
||||
}
|
||||
|
||||
doStop () {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
// Private: Detach any event subscribers.
|
||||
dispose () {
|
||||
this.emitter.dispose()
|
||||
@@ -324,6 +166,129 @@ class NativeWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Emulate a "filesystem watcher" by subscribing to Atom events like buffers being saved. This will miss
|
||||
// any changes made to files outside of Atom, but it also has no overhead.
|
||||
class AtomNativeWatcher extends NativeWatcher {
|
||||
async doStart () {
|
||||
const getRealPath = givenPath => {
|
||||
return new Promise(resolve => {
|
||||
fs.realpath(givenPath, (err, resolvedPath) => {
|
||||
err ? resolve(null) : resolve(resolvedPath)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.subs.add(atom.workspace.observeTextEditors(async editor => {
|
||||
let realPath = await getRealPath(editor.getPath())
|
||||
if (!realPath || !realPath.startsWith(this.normalizedPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
const announce = (action, oldPath) => {
|
||||
const payload = {action, path: realPath}
|
||||
if (oldPath) payload.oldPath = oldPath
|
||||
this.onEvents([payload])
|
||||
}
|
||||
|
||||
const buffer = editor.getBuffer()
|
||||
|
||||
this.subs.add(buffer.onDidConflict(() => announce('modified')))
|
||||
this.subs.add(buffer.onDidReload(() => announce('modified')))
|
||||
this.subs.add(buffer.onDidSave(event => {
|
||||
if (event.path === realPath) {
|
||||
announce('modified')
|
||||
} else {
|
||||
const oldPath = realPath
|
||||
realPath = event.path
|
||||
announce('renamed', oldPath)
|
||||
}
|
||||
}))
|
||||
|
||||
this.subs.add(buffer.onDidDelete(() => announce('deleted')))
|
||||
|
||||
this.subs.add(buffer.onDidChangePath(newPath => {
|
||||
if (newPath !== this.normalizedPath) {
|
||||
const oldPath = this.normalizedPath
|
||||
this.normalizedPath = newPath
|
||||
announce('renamed', oldPath)
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
// Giant-ass brittle hack to hook files (and eventually directories) created from the TreeView.
|
||||
const treeViewPackage = await atom.packages.getLoadedPackage('tree-view')
|
||||
if (!treeViewPackage) return
|
||||
await treeViewPackage.activationPromise
|
||||
const treeViewModule = treeViewPackage.mainModule
|
||||
if (!treeViewModule) return
|
||||
const treeView = treeViewModule.getTreeViewInstance()
|
||||
|
||||
const isOpenInEditor = async eventPath => {
|
||||
const openPaths = await Promise.all(
|
||||
atom.workspace.getTextEditors().map(editor => getRealPath(editor.getPath()))
|
||||
)
|
||||
return openPaths.includes(eventPath)
|
||||
}
|
||||
|
||||
this.subs.add(treeView.onFileCreated(async event => {
|
||||
const realPath = await getRealPath(event.path)
|
||||
if (!realPath) return
|
||||
|
||||
this.onEvents([{action: 'added', path: realPath}])
|
||||
}))
|
||||
|
||||
this.subs.add(treeView.onEntryDeleted(async event => {
|
||||
const realPath = await getRealPath(event.path)
|
||||
if (!realPath || isOpenInEditor(realPath)) return
|
||||
|
||||
this.onEvents([{action: 'deleted', path: realPath}])
|
||||
}))
|
||||
|
||||
this.subs.add(treeView.onEntryMoved(async event => {
|
||||
const [realNewPath, realOldPath] = await Promise.all([
|
||||
getRealPath(event.newPath),
|
||||
getRealPath(event.initialPath)
|
||||
])
|
||||
if (!realNewPath || !realOldPath || isOpenInEditor(realNewPath) || isOpenInEditor(realOldPath)) return
|
||||
|
||||
this.onEvents([{action: 'renamed', path: realNewPath, oldPath: realOldPath}])
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Implement a native watcher by translating events from an NSFW watcher.
|
||||
class NSFWNativeWatcher extends NativeWatcher {
|
||||
async doStart (rootPath, eventCallback, errorCallback) {
|
||||
const handler = events => {
|
||||
this.onEvents(events.map(event => {
|
||||
const action = ACTION_MAP.get(event.action) || `unexpected (${event.action})`
|
||||
const payload = {action}
|
||||
|
||||
if (event.file) {
|
||||
payload.path = path.join(event.directory, event.file)
|
||||
} else {
|
||||
payload.oldPath = path.join(event.directory, event.oldFile)
|
||||
payload.path = path.join(event.directory, event.newFile)
|
||||
}
|
||||
|
||||
return payload
|
||||
}))
|
||||
}
|
||||
|
||||
this.watcher = await nsfw(
|
||||
this.normalizedPath,
|
||||
handler,
|
||||
{debounceMS: 100, errorCallback: this.onError}
|
||||
)
|
||||
|
||||
await this.watcher.start()
|
||||
}
|
||||
|
||||
doStop () {
|
||||
return this.watcher.stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Extended: Manage a subscription to filesystem events that occur beneath a root directory. Construct these by
|
||||
// calling `watchPath`. To watch for events within active project directories, use {Project::onDidChangeFiles}
|
||||
// instead.
|
||||
@@ -384,6 +349,15 @@ class PathWatcher {
|
||||
this.native = null
|
||||
this.changeCallbacks = new Map()
|
||||
|
||||
this.attachedPromise = new Promise(resolve => {
|
||||
this.resolveAttachedPromise = resolve
|
||||
})
|
||||
|
||||
this.startPromise = new Promise((resolve, reject) => {
|
||||
this.resolveStartPromise = resolve
|
||||
this.rejectStartPromise = reject
|
||||
})
|
||||
|
||||
this.normalizedPathPromise = new Promise((resolve, reject) => {
|
||||
fs.realpath(watchedPath, (err, real) => {
|
||||
if (err) {
|
||||
@@ -395,13 +369,7 @@ class PathWatcher {
|
||||
resolve(real)
|
||||
})
|
||||
})
|
||||
|
||||
this.attachedPromise = new Promise(resolve => {
|
||||
this.resolveAttachedPromise = resolve
|
||||
})
|
||||
this.startPromise = new Promise(resolve => {
|
||||
this.resolveStartPromise = resolve
|
||||
})
|
||||
this.normalizedPathPromise.catch(err => this.rejectStartPromise(err))
|
||||
|
||||
this.emitter = new Emitter()
|
||||
this.subs = new CompositeDisposable()
|
||||
@@ -543,46 +511,139 @@ class PathWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Globally tracked state used to de-duplicate related [PathWatchers]{PathWatcher}.
|
||||
// Private: Globally tracked state used to de-duplicate related [PathWatchers]{PathWatcher} backed by emulated Atom
|
||||
// events or NSFW.
|
||||
class PathWatcherManager {
|
||||
|
||||
// Private: Access or lazily initialize the singleton manager instance.
|
||||
//
|
||||
// Returns the one and only {PathWatcherManager}.
|
||||
static instance () {
|
||||
if (!PathWatcherManager.theManager) {
|
||||
PathWatcherManager.theManager = new PathWatcherManager()
|
||||
// Private: Access the currently active manager instance, creating one if necessary.
|
||||
static active () {
|
||||
if (!this.activeManager) {
|
||||
this.activeManager = new PathWatcherManager(atom.config.get('core.fileSystemWatcher'))
|
||||
this.sub = atom.config.onDidChange('core.fileSystemWatcher', ({newValue}) => { this.transitionTo(newValue) })
|
||||
}
|
||||
return PathWatcherManager.theManager
|
||||
return this.activeManager
|
||||
}
|
||||
|
||||
// Private: Replace the active {PathWatcherManager} with a new one that creates [NativeWatchers]{NativeWatcher}
|
||||
// based on the value of `setting`.
|
||||
static async transitionTo (setting) {
|
||||
const current = this.active()
|
||||
|
||||
if (this.transitionPromise) {
|
||||
await this.transitionPromise
|
||||
}
|
||||
|
||||
if (current.setting === setting) {
|
||||
return
|
||||
}
|
||||
current.isShuttingDown = true
|
||||
|
||||
let resolveTransitionPromise = () => {}
|
||||
this.transitionPromise = new Promise(resolve => {
|
||||
resolveTransitionPromise = resolve
|
||||
})
|
||||
|
||||
const replacement = new PathWatcherManager(setting)
|
||||
this.activeManager = replacement
|
||||
|
||||
await Promise.all(
|
||||
Array.from(current.live, async ([root, native]) => {
|
||||
const w = await replacement.createWatcher(root, {}, () => {})
|
||||
native.reattachTo(w.native, root, w.native.options || {})
|
||||
})
|
||||
)
|
||||
|
||||
current.stopAllWatchers()
|
||||
|
||||
resolveTransitionPromise()
|
||||
this.transitionPromise = null
|
||||
}
|
||||
|
||||
// Private: Initialize global {PathWatcher} state.
|
||||
constructor () {
|
||||
this.live = new Set()
|
||||
this.nativeRegistry = new NativeWatcherRegistry(
|
||||
normalizedPath => {
|
||||
const nativeWatcher = new NativeWatcher(normalizedPath)
|
||||
constructor (setting) {
|
||||
this.setting = setting
|
||||
this.live = new Map()
|
||||
|
||||
this.live.add(nativeWatcher)
|
||||
const sub = nativeWatcher.onWillStop(() => {
|
||||
this.live.delete(nativeWatcher)
|
||||
sub.dispose()
|
||||
})
|
||||
const initLocal = NativeConstructor => {
|
||||
this.nativeRegistry = new NativeWatcherRegistry(
|
||||
normalizedPath => {
|
||||
const nativeWatcher = new NativeConstructor(normalizedPath)
|
||||
|
||||
return nativeWatcher
|
||||
}
|
||||
)
|
||||
this.live.set(normalizedPath, nativeWatcher)
|
||||
const sub = nativeWatcher.onWillStop(() => {
|
||||
this.live.delete(normalizedPath)
|
||||
sub.dispose()
|
||||
})
|
||||
|
||||
return nativeWatcher
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (setting === 'atom') {
|
||||
initLocal(AtomNativeWatcher)
|
||||
} else if (setting === 'experimental') {
|
||||
//
|
||||
} else if (setting === 'poll') {
|
||||
//
|
||||
} else {
|
||||
initLocal(NSFWNativeWatcher)
|
||||
}
|
||||
|
||||
this.isShuttingDown = false
|
||||
}
|
||||
|
||||
useExperimentalWatcher () {
|
||||
return this.setting === 'experimental' || this.setting === 'poll'
|
||||
}
|
||||
|
||||
// Private: Create a {PathWatcher} tied to this global state. See {watchPath} for detailed arguments.
|
||||
createWatcher (rootPath, options, eventCallback) {
|
||||
const watcher = new PathWatcher(this.nativeRegistry, rootPath, options)
|
||||
watcher.onDidChange(eventCallback)
|
||||
return watcher
|
||||
async createWatcher (rootPath, options, eventCallback) {
|
||||
if (this.isShuttingDown) {
|
||||
await this.constructor.transitionPromise
|
||||
return PathWatcherManager.active().createWatcher(rootPath, options, eventCallback)
|
||||
}
|
||||
|
||||
if (this.useExperimentalWatcher()) {
|
||||
if (this.setting === 'poll') {
|
||||
options.poll = true
|
||||
}
|
||||
|
||||
const w = await watcher.watchPath(rootPath, options, eventCallback)
|
||||
this.live.set(rootPath, w.native)
|
||||
return w
|
||||
}
|
||||
|
||||
const w = new PathWatcher(this.nativeRegistry, rootPath, options)
|
||||
w.onDidChange(eventCallback)
|
||||
await w.getStartPromise()
|
||||
return w
|
||||
}
|
||||
|
||||
// Private: Directly access the {NativeWatcherRegistry}.
|
||||
getRegistry () {
|
||||
if (this.useExperimentalWatcher()) {
|
||||
return watcher.getRegistry()
|
||||
}
|
||||
|
||||
return this.nativeRegistry
|
||||
}
|
||||
|
||||
// Private: Sample watcher usage statistics. Only available for experimental watchers.
|
||||
status () {
|
||||
if (this.useExperimentalWatcher()) {
|
||||
return watcher.status()
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// Private: Return a {String} depicting the currently active native watchers.
|
||||
print () {
|
||||
if (this.useExperimentalWatcher()) {
|
||||
return watcher.printWatchers()
|
||||
}
|
||||
|
||||
return this.nativeRegistry.print()
|
||||
}
|
||||
|
||||
@@ -590,8 +651,12 @@ class PathWatcherManager {
|
||||
//
|
||||
// Returns a {Promise} that resolves when all native watcher resources are disposed.
|
||||
stopAllWatchers () {
|
||||
if (this.useExperimentalWatcher()) {
|
||||
return watcher.stopAllWatchers()
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
Array.from(this.live, watcher => watcher.stop())
|
||||
Array.from(this.live, ([, w]) => w.stop())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -636,19 +701,33 @@ class PathWatcherManager {
|
||||
// ```
|
||||
//
|
||||
function watchPath (rootPath, options, eventCallback) {
|
||||
const watcher = PathWatcherManager.instance().createWatcher(rootPath, options, eventCallback)
|
||||
return watcher.getStartPromise().then(() => watcher)
|
||||
return PathWatcherManager.active().createWatcher(rootPath, options, eventCallback)
|
||||
}
|
||||
|
||||
// Private: Return a Promise that resolves when all {NativeWatcher} instances associated with a FileSystemManager
|
||||
// have stopped listening. This is useful for `afterEach()` blocks in unit tests.
|
||||
function stopAllWatchers () {
|
||||
return PathWatcherManager.instance().stopAllWatchers()
|
||||
return PathWatcherManager.active().stopAllWatchers()
|
||||
}
|
||||
|
||||
// Private: Show the currently active native watchers.
|
||||
function printWatchers () {
|
||||
return PathWatcherManager.instance().print()
|
||||
// Private: Show the currently active native watchers in a formatted {String}.
|
||||
watchPath.printWatchers = function () {
|
||||
return PathWatcherManager.active().print()
|
||||
}
|
||||
|
||||
module.exports = {watchPath, stopAllWatchers, printWatchers}
|
||||
// Private: Access the active {NativeWatcherRegistry}.
|
||||
watchPath.getRegistry = function () {
|
||||
return PathWatcherManager.active().getRegistry()
|
||||
}
|
||||
|
||||
// Private: Sample usage statistics for the active watcher.
|
||||
watchPath.status = function () {
|
||||
return PathWatcherManager.active().status()
|
||||
}
|
||||
|
||||
// Private: Configure @atom/watcher ("experimental") directly.
|
||||
watchPath.configure = function (...args) {
|
||||
return watcher.configure(...args)
|
||||
}
|
||||
|
||||
module.exports = {watchPath, stopAllWatchers}
|
||||
|
||||
@@ -266,14 +266,22 @@ class TextEditorComponent {
|
||||
if (useScheduler === true) {
|
||||
const scheduler = etch.getScheduler()
|
||||
scheduler.readDocument(() => {
|
||||
this.measureContentDuringUpdateSync()
|
||||
const restartFrame = this.measureContentDuringUpdateSync()
|
||||
scheduler.updateDocument(() => {
|
||||
this.updateSyncAfterMeasuringContent()
|
||||
if (restartFrame) {
|
||||
this.updateSync(true)
|
||||
} else {
|
||||
this.updateSyncAfterMeasuringContent()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.measureContentDuringUpdateSync()
|
||||
this.updateSyncAfterMeasuringContent()
|
||||
const restartFrame = this.measureContentDuringUpdateSync()
|
||||
if (restartFrame) {
|
||||
this.updateSync(false)
|
||||
} else {
|
||||
this.updateSyncAfterMeasuringContent()
|
||||
}
|
||||
}
|
||||
|
||||
this.updateScheduled = false
|
||||
@@ -391,15 +399,16 @@ class TextEditorComponent {
|
||||
this.measureHorizontalPositions()
|
||||
this.updateAbsolutePositionedDecorations()
|
||||
|
||||
const isHorizontalScrollbarVisible = (
|
||||
this.canScrollHorizontally() &&
|
||||
this.getHorizontalScrollbarHeight() > 0
|
||||
)
|
||||
|
||||
if (this.pendingAutoscroll) {
|
||||
this.derivedDimensionsCache = {}
|
||||
const {screenRange, options} = this.pendingAutoscroll
|
||||
this.autoscrollHorizontally(screenRange, options)
|
||||
|
||||
const isHorizontalScrollbarVisible = (
|
||||
this.canScrollHorizontally() &&
|
||||
this.getHorizontalScrollbarHeight() > 0
|
||||
)
|
||||
if (!wasHorizontalScrollbarVisible && isHorizontalScrollbarVisible) {
|
||||
this.autoscrollVertically(screenRange, options)
|
||||
}
|
||||
@@ -408,6 +417,8 @@ class TextEditorComponent {
|
||||
|
||||
this.linesToMeasure.clear()
|
||||
this.measuredContent = true
|
||||
|
||||
return wasHorizontalScrollbarVisible !== isHorizontalScrollbarVisible
|
||||
}
|
||||
|
||||
updateSyncAfterMeasuringContent () {
|
||||
@@ -1520,15 +1531,11 @@ class TextEditorComponent {
|
||||
let {wheelDeltaX, wheelDeltaY} = event
|
||||
|
||||
if (Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)) {
|
||||
wheelDeltaX = (Math.sign(wheelDeltaX) === 1)
|
||||
? Math.max(1, wheelDeltaX * scrollSensitivity)
|
||||
: Math.min(-1, wheelDeltaX * scrollSensitivity)
|
||||
wheelDeltaX = wheelDeltaX * scrollSensitivity
|
||||
wheelDeltaY = 0
|
||||
} else {
|
||||
wheelDeltaX = 0
|
||||
wheelDeltaY = (Math.sign(wheelDeltaY) === 1)
|
||||
? Math.max(1, wheelDeltaY * scrollSensitivity)
|
||||
: Math.min(-1, wheelDeltaY * scrollSensitivity)
|
||||
wheelDeltaY = wheelDeltaY * scrollSensitivity
|
||||
}
|
||||
|
||||
if (this.getPlatform() !== 'darwin' && event.shiftKey) {
|
||||
|
||||
@@ -205,7 +205,7 @@ class TextEditorRegistry {
|
||||
// Returns a {String} scope name, or `null` if no override has been set
|
||||
// for the given editor.
|
||||
getGrammarOverride (editor) {
|
||||
return editor.getBuffer().getLanguageMode().grammar.scopeName
|
||||
return atom.grammars.getAssignedLanguageId(editor.getBuffer())
|
||||
}
|
||||
|
||||
// Deprecated: Remove any grammar override that has been set for the given {TextEditor}.
|
||||
|
||||
@@ -11,6 +11,7 @@ const Cursor = require('./cursor')
|
||||
const Selection = require('./selection')
|
||||
const NullGrammar = require('./null-grammar')
|
||||
const TextMateLanguageMode = require('./text-mate-language-mode')
|
||||
const ScopeDescriptor = require('./scope-descriptor')
|
||||
|
||||
const TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
const GutterContainer = require('./gutter-container')
|
||||
@@ -3655,7 +3656,10 @@ class TextEditor {
|
||||
//
|
||||
// Returns a {ScopeDescriptor}.
|
||||
scopeDescriptorForBufferPosition (bufferPosition) {
|
||||
return this.buffer.getLanguageMode().scopeDescriptorForPosition(bufferPosition)
|
||||
const languageMode = this.buffer.getLanguageMode()
|
||||
return languageMode.scopeDescriptorForPosition
|
||||
? languageMode.scopeDescriptorForPosition(bufferPosition)
|
||||
: new ScopeDescriptor({scopes: ['text']})
|
||||
}
|
||||
|
||||
// Extended: Get the range in buffer coordinates of all tokens surrounding the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const {Document} = require('tree-sitter')
|
||||
const {Point, Range, Emitter} = require('atom')
|
||||
const {Point, Range} = require('text-buffer')
|
||||
const {Emitter, Disposable} = require('event-kit')
|
||||
const ScopeDescriptor = require('./scope-descriptor')
|
||||
const TokenizedLine = require('./tokenized-line')
|
||||
const TextMateLanguageMode = require('./text-mate-language-mode')
|
||||
@@ -279,10 +280,16 @@ class TreeSitterLanguageMode {
|
||||
if (node) return new Range(node.startPosition, node.endPosition)
|
||||
}
|
||||
|
||||
bufferRangeForScopeAtPosition (position) {
|
||||
return this.getRangeForSyntaxNodeContainingRange(new Range(position, position))
|
||||
}
|
||||
|
||||
/*
|
||||
Section - Backward compatibility shims
|
||||
*/
|
||||
|
||||
onDidTokenize (callback) { return new Disposable(() => {}) }
|
||||
|
||||
tokenizedLineForRow (row) {
|
||||
return new TokenizedLine({
|
||||
openScopes: [],
|
||||
@@ -296,6 +303,7 @@ class TreeSitterLanguageMode {
|
||||
}
|
||||
|
||||
scopeDescriptorForPosition (point) {
|
||||
point = Point.fromObject(point)
|
||||
const result = []
|
||||
let node = this.document.rootNode.descendantForPosition(point)
|
||||
|
||||
@@ -413,7 +421,7 @@ class TreeSitterHighlightIterator {
|
||||
this.pushCloseTag()
|
||||
|
||||
const {nextSibling} = this.currentNode
|
||||
if (nextSibling) {
|
||||
if (nextSibling && nextSibling.endIndex > this.currentIndex) {
|
||||
this.currentNode = nextSibling
|
||||
this.currentChildIndex++
|
||||
if (this.currentIndex === nextSibling.startIndex) {
|
||||
@@ -427,19 +435,8 @@ class TreeSitterHighlightIterator {
|
||||
if (!this.currentNode) break
|
||||
}
|
||||
}
|
||||
} else if (this.currentNode.startIndex < this.currentNode.endIndex) {
|
||||
this.currentNode = this.currentNode.nextSibling
|
||||
if (this.currentNode) {
|
||||
this.currentChildIndex++
|
||||
this.currentPosition = this.currentNode.startPosition
|
||||
this.currentIndex = this.currentNode.startIndex
|
||||
this.pushOpenTag()
|
||||
this.descendLeft()
|
||||
}
|
||||
} else {
|
||||
this.pushCloseTag()
|
||||
this.currentNode = this.currentNode.parent
|
||||
this.currentChildIndex = last(this.containingNodeChildIndices)
|
||||
this.currentNode = this.currentNode.nextSibling
|
||||
}
|
||||
} while (this.closeTags.length === 0 && this.openTags.length === 0 && this.currentNode)
|
||||
|
||||
@@ -495,16 +492,22 @@ class TreeSitterHighlightIterator {
|
||||
class TreeSitterTextBufferInput {
|
||||
constructor (buffer) {
|
||||
this.buffer = buffer
|
||||
this.seek(0)
|
||||
this.position = {row: 0, column: 0}
|
||||
this.isBetweenCRLF = false
|
||||
}
|
||||
|
||||
seek (characterIndex) {
|
||||
this.position = this.buffer.positionForCharacterIndex(characterIndex)
|
||||
seek (offset, position) {
|
||||
this.position = position
|
||||
this.isBetweenCRLF = this.position.column > this.buffer.lineLengthForRow(this.position.row)
|
||||
}
|
||||
|
||||
read () {
|
||||
const endPosition = this.buffer.clipPosition(this.position.traverse({row: 1000, column: 0}))
|
||||
const text = this.buffer.getTextInRange([this.position, endPosition])
|
||||
const endPosition = this.buffer.clipPosition(new Point(this.position.row + 1000, 0))
|
||||
let text = this.buffer.getTextInRange([this.position, endPosition])
|
||||
if (this.isBetweenCRLF) {
|
||||
text = text.slice(1)
|
||||
this.isBetweenCRLF = false
|
||||
}
|
||||
this.position = endPosition
|
||||
return text
|
||||
}
|
||||
|
||||
@@ -56,10 +56,10 @@ class WorkspaceElement extends HTMLElement {
|
||||
}
|
||||
|
||||
updateGlobalTextEditorStyleSheet () {
|
||||
const styleSheetSource = `atom-text-editor {
|
||||
font-size: ${this.config.get('editor.fontSize')}px;
|
||||
font-family: ${this.config.get('editor.fontFamily')};
|
||||
line-height: ${this.config.get('editor.lineHeight')};
|
||||
const styleSheetSource = `atom-workspace {
|
||||
--editor-font-size: ${this.config.get('editor.fontSize')}px;
|
||||
--editor-font-family: ${this.config.get('editor.fontFamily')};
|
||||
--editor-line-height: ${this.config.get('editor.lineHeight')};
|
||||
}`
|
||||
this.styleManager.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles', priority: -1})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@ibeam-2x: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC');
|
||||
|
||||
.cursor-white() {
|
||||
cursor: -webkit-image-set(@ibeam-1x 1x, @ibeam-2x 2x) 5 8, text;
|
||||
cursor: -webkit-image-set(@ibeam-1x 1dppx, @ibeam-2x 2dppx) 5 8, text;
|
||||
}
|
||||
|
||||
// Editors
|
||||
|
||||
@@ -113,10 +113,16 @@ atom-dock {
|
||||
// Promote to own layer, fixes rendering issue atom/atom#14915
|
||||
will-change: transform;
|
||||
|
||||
&.right { left: 0; }
|
||||
&.bottom { top: 0; }
|
||||
&.left { right: 0; }
|
||||
}
|
||||
&.right {
|
||||
left: 0;
|
||||
}
|
||||
&.bottom {
|
||||
top: 0;
|
||||
}
|
||||
&.left {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the button.
|
||||
&:not(.atom-dock-toggle-button-visible) {
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
@import "octicon-utf-codes";
|
||||
@import "octicon-mixins";
|
||||
|
||||
:root {
|
||||
// Fixes specs
|
||||
--editor-font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||
}
|
||||
|
||||
atom-text-editor {
|
||||
display: flex;
|
||||
font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||
cursor: text;
|
||||
font-family: var(--editor-font-family);
|
||||
font-size: var(--editor-font-size);
|
||||
line-height: var(--editor-line-height);
|
||||
|
||||
.gutter-container {
|
||||
width: min-content;
|
||||
|
||||
@@ -81,5 +81,5 @@
|
||||
|
||||
// Other
|
||||
|
||||
@font-family: 'BlinkMacSystemFont', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif;
|
||||
@font-family: system-ui;
|
||||
@use-custom-controls: true; // false uses native controls
|
||||
|
||||
23
stylelint.config.js
Normal file
23
stylelint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
"extends": "stylelint-config-standard",
|
||||
"ignoreFiles": [path.resolve(__dirname, "static", "atom.less")],
|
||||
"rules": {
|
||||
"color-hex-case": null, // TODO: enable?
|
||||
"max-empty-lines": null, // TODO: enable?
|
||||
"selector-type-no-unknown": null,
|
||||
"function-comma-space-after": null, // TODO: enable?
|
||||
"font-family-no-missing-generic-family-keyword": null, // needed for octicons (no sensible fallback)
|
||||
"declaration-empty-line-before": null, // TODO: enable?
|
||||
"declaration-block-trailing-semicolon": null, // TODO: enable
|
||||
"no-descending-specificity": null,
|
||||
"number-leading-zero": null, // TODO: enable?
|
||||
"no-duplicate-selectors": null,
|
||||
"selector-pseudo-element-colon-notation": null, // TODO: enable?
|
||||
"selector-list-comma-newline-after": null, // TODO: enable?
|
||||
"rule-empty-line-before": null, // TODO: enable?
|
||||
"at-rule-empty-line-before": null, // TODO: enable?
|
||||
"font-family-no-duplicate-names": null, // TODO: enable?
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user