Merge pull request #664 from pulsar-edit/manual-decaf-bundle

Manual Decaf Bundle (`autocomplete-atom-api`, `autoflow`, `deprecation-cop`) Source
This commit is contained in:
confused_techie 2023-08-09 17:18:43 -07:00 committed by GitHub
commit 21fedaee87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 401 additions and 313 deletions

View File

@ -1,6 +0,0 @@
provider = require './provider'
module.exports =
activate: -> provider.load()
getProvider: -> provider

View File

@ -0,0 +1,7 @@
const provider = require('./provider');
module.exports = {
activate() { return provider.load(); },
getProvider() { return provider; }
};

View File

@ -1,97 +0,0 @@
fs = require 'fs'
path = require 'path'
CLASSES = require('../completions.json')
propertyPrefixPattern = /(?:^|\[|\(|,|=|:|\s)\s*(atom\.(?:[a-zA-Z]+\.?){0,2})$/
module.exports =
selector: '.source.coffee, .source.js'
filterSuggestions: true
getSuggestions: ({bufferPosition, editor}) ->
return unless @isEditingAnAtomPackageFile(editor)
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
@getCompletions(line)
load: ->
@loadCompletions()
atom.project.onDidChangePaths => @scanProjectDirectories()
@scanProjectDirectories()
scanProjectDirectories: ->
@packageDirectories = []
atom.project.getDirectories().forEach (directory) =>
return unless directory?
@readMetadata directory, (error, metadata) =>
if @isAtomPackage(metadata) or @isAtomCore(metadata)
@packageDirectories.push(directory)
readMetadata: (directory, callback) ->
fs.readFile path.join(directory.getPath(), 'package.json'), (error, contents) ->
unless error?
try
metadata = JSON.parse(contents)
catch parseError
error = parseError
callback(error, metadata)
isAtomPackage: (metadata) ->
metadata?.engines?.atom?.length > 0
isAtomCore: (metadata) ->
metadata?.name is 'atom'
isEditingAnAtomPackageFile: (editor) ->
editorPath = editor.getPath()
if editorPath?
parsedPath = path.parse(editorPath)
basename = path.basename(parsedPath.dir)
if basename is '.atom' or basename is '.pulsar'
if parsedPath.base is 'init.coffee' or parsedPath.base is 'init.js'
return true
for directory in @packageDirectories ? []
return true if directory.contains(editorPath)
false
loadCompletions: ->
@completions ?= {}
@loadProperty('atom', 'AtomEnvironment', CLASSES)
getCompletions: (line) ->
completions = []
match = propertyPrefixPattern.exec(line)?[1]
return completions unless match
segments = match.split('.')
prefix = segments.pop() ? ''
segments = segments.filter (segment) -> segment
property = segments[segments.length - 1]
propertyCompletions = @completions[property]?.completions ? []
for completion in propertyCompletions when not prefix or firstCharsEqual(completion.name, prefix)
completions.push(clone(completion))
completions
getPropertyClass: (name) ->
atom[name]?.constructor?.name
loadProperty: (propertyName, className, classes, parent) ->
classCompletions = classes[className]
return unless classCompletions?
@completions[propertyName] = completions: []
for completion in classCompletions
@completions[propertyName].completions.push(completion)
if completion.type is 'property'
propertyClass = @getPropertyClass(completion.name)
@loadProperty(completion.name, propertyClass, classes)
return
clone = (obj) ->
newObj = {}
newObj[k] = v for k, v of obj
newObj
firstCharsEqual = (str1, str2) ->
str1[0].toLowerCase() is str2[0].toLowerCase()

View File

@ -0,0 +1,125 @@
const fs = require('fs');
const path = require('path');
const CLASSES = require('../completions.json');
const propertyPrefixPattern = /(?:^|\[|\(|,|=|:|\s)\s*(atom\.(?:[a-zA-Z]+\.?){0,2})$/;
module.exports = {
selector: '.source.coffee, .source.js',
filterSuggestions: true,
getSuggestions({bufferPosition, editor}) {
if (!this.isEditingAnAtomPackageFile(editor)) { return; }
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
return this.getCompletions(line);
},
load() {
this.loadCompletions();
atom.project.onDidChangePaths(() => this.scanProjectDirectories());
return this.scanProjectDirectories();
},
scanProjectDirectories() {
this.packageDirectories = [];
atom.project.getDirectories().forEach(directory => {
if (directory == null) { return; }
this.readMetadata(directory, (error, metadata) => {
if (this.isAtomPackage(metadata) || this.isAtomCore(metadata)) {
this.packageDirectories.push(directory);
}
});
});
},
readMetadata(directory, callback) {
fs.readFile(path.join(directory.getPath(), 'package.json'), function(error, contents) {
let metadata;
if (error == null) {
try {
metadata = JSON.parse(contents);
} catch (parseError) {
error = parseError;
}
}
return callback(error, metadata);
});
},
isAtomPackage(metadata) {
return metadata?.engines?.atom?.length > 0;
},
isAtomCore(metadata) {
return metadata?.name === 'atom';
},
isEditingAnAtomPackageFile(editor) {
const editorPath = editor.getPath();
if (editorPath != null) {
const parsedPath = path.parse(editorPath);
const basename = path.basename(parsedPath.dir);
if ((basename === '.atom') || (basename === '.pulsar')) {
if ((parsedPath.base === 'init.coffee') || (parsedPath.base === 'init.js')) {
return true;
}
}
}
for (let directory of (this.packageDirectories != null ? this.packageDirectories : [])) {
if (directory.contains(editorPath)) { return true; }
}
return false;
},
loadCompletions() {
if (this.completions == null) { this.completions = {}; }
return this.loadProperty('atom', 'AtomEnvironment', CLASSES);
},
getCompletions(line) {
const completions = [];
const match = propertyPrefixPattern.exec(line)?.[1];
if (!match) { return completions; }
let segments = match.split('.');
const prefix = segments.pop() ?? '';
segments = segments.filter(segment => segment);
const property = segments[segments.length - 1];
const propertyCompletions = this.completions[property]?.completions != null ? this.completions[property]?.completions : [];
for (let completion of propertyCompletions) {
if (!prefix || firstCharsEqual(completion.name, prefix)) {
completions.push(clone(completion));
}
}
return completions;
},
getPropertyClass(name) {
return atom[name]?.constructor?.name;
},
loadProperty(propertyName, className, classes, parent) {
const classCompletions = classes[className];
if (classCompletions == null) { return; }
this.completions[propertyName] = {completions: []};
for (let completion of classCompletions) {
this.completions[propertyName].completions.push(completion);
if (completion.type === 'property') {
const propertyClass = this.getPropertyClass(completion.name);
this.loadProperty(completion.name, propertyClass, classes);
}
}
}
};
const clone = function(obj) {
const newObj = {};
for (let k in obj) { const v = obj[k]; newObj[k] = v; }
return newObj;
};
const firstCharsEqual = (str1, str2) => str1[0].toLowerCase() === str2[0].toLowerCase();

View File

@ -1,142 +0,0 @@
_ = require 'underscore-plus'
CharacterPattern = ///
[
^\s
]
///
module.exports =
activate: ->
@commandDisposable = atom.commands.add 'atom-text-editor',
'autoflow:reflow-selection': (event) =>
@reflowSelection(event.currentTarget.getModel())
deactivate: ->
@commandDisposable?.dispose()
@commandDisposable = null
reflowSelection: (editor) ->
range = editor.getSelectedBufferRange()
range = editor.getCurrentParagraphBufferRange() if range.isEmpty()
return unless range?
reflowOptions =
wrapColumn: @getPreferredLineLength(editor)
tabLength: @getTabLength(editor)
reflowedText = @reflow(editor.getTextInRange(range), reflowOptions)
editor.getBuffer().setTextInRange(range, reflowedText)
reflow: (text, {wrapColumn, tabLength}) ->
paragraphs = []
# Convert all \r\n and \r to \n. The text buffer will normalize them later
text = text.replace(/\r\n?/g, '\n')
leadingVerticalSpace = text.match(/^\s*\n/)
if leadingVerticalSpace
text = text.substr(leadingVerticalSpace.length)
else
leadingVerticalSpace = ''
trailingVerticalSpace = text.match(/\n\s*$/)
if trailingVerticalSpace
text = text.substr(0, text.length - trailingVerticalSpace.length)
else
trailingVerticalSpace = ''
paragraphBlocks = text.split(/\n\s*\n/g)
if tabLength
tabLengthInSpaces = Array(tabLength + 1).join(' ')
else
tabLengthInSpaces = ''
for block in paragraphBlocks
blockLines = block.split('\n')
# For LaTeX tags surrounding the text, we simply ignore them, and
# reproduce them verbatim in the wrapped text.
beginningLinesToIgnore = []
endingLinesToIgnore = []
latexTagRegex = /^\s*\\\w+(\[.*\])?\{\w+\}(\[.*\])?\s*$/g # e.g. \begin{verbatim}
latexTagStartRegex = /^\s*\\\w+\s*\{\s*$/g # e.g. \item{
latexTagEndRegex = /^\s*\}\s*$/g # e.g. }
while blockLines.length > 0 and (
blockLines[0].match(latexTagRegex) or
blockLines[0].match(latexTagStartRegex))
beginningLinesToIgnore.push(blockLines[0])
blockLines.shift()
while blockLines.length > 0 and (
blockLines[blockLines.length - 1].match(latexTagRegex) or
blockLines[blockLines.length - 1].match(latexTagEndRegex))
endingLinesToIgnore.unshift(blockLines[blockLines.length - 1])
blockLines.pop()
# The paragraph might be a LaTeX section with no text, only tags:
# \documentclass{article}
# In that case, we have nothing to reflow.
# Push the tags verbatim and continue to the next paragraph.
unless blockLines.length > 0
paragraphs.push(block)
continue
# TODO: this could be more language specific. Use the actual comment char.
# Remember that `-` has to be the last character in the character class.
linePrefix = blockLines[0].match(/^\s*(\/\/|\/\*|;;|#'|\|\|\||--|[#%*>-])?\s*/g)[0]
linePrefixTabExpanded = linePrefix
if tabLengthInSpaces
linePrefixTabExpanded = linePrefix.replace(/\t/g, tabLengthInSpaces)
if linePrefix
escapedLinePrefix = _.escapeRegExp(linePrefix)
blockLines = blockLines.map (blockLine) ->
blockLine.replace(///^#{escapedLinePrefix}///, '')
blockLines = blockLines.map (blockLine) ->
blockLine.replace(/^\s+/, '')
lines = []
currentLine = []
currentLineLength = linePrefixTabExpanded.length
wrappedLinePrefix = linePrefix
.replace(/^(\s*)\/\*/, '$1 ')
.replace(/^(\s*)-(?!-)/, '$1 ')
firstLine = true
for segment in @segmentText(blockLines.join(' '))
if @wrapSegment(segment, currentLineLength, wrapColumn)
# Independent of line prefix don't mess with it on the first line
if firstLine isnt true
# Handle C comments
if linePrefix.search(/^\s*\/\*/) isnt -1 or linePrefix.search(/^\s*-(?!-)/) isnt -1
linePrefix = wrappedLinePrefix
lines.push(linePrefix + currentLine.join(''))
currentLine = []
currentLineLength = linePrefixTabExpanded.length
firstLine = false
currentLine.push(segment)
currentLineLength += segment.length
lines.push(linePrefix + currentLine.join(''))
wrappedLines = beginningLinesToIgnore.concat(lines.concat(endingLinesToIgnore))
paragraphs.push(wrappedLines.join('\n').replace(/\s+\n/g, '\n'))
return leadingVerticalSpace + paragraphs.join('\n\n') + trailingVerticalSpace
getTabLength: (editor) ->
atom.config.get('editor.tabLength', scope: editor.getRootScopeDescriptor()) ? 2
getPreferredLineLength: (editor) ->
atom.config.get('editor.preferredLineLength', scope: editor.getRootScopeDescriptor())
wrapSegment: (segment, currentLineLength, wrapColumn) ->
CharacterPattern.test(segment) and
(currentLineLength + segment.length > wrapColumn) and
(currentLineLength > 0 or segment.length < wrapColumn)
segmentText: (text) ->
segments = []
re = /[\s]+|[^\s]+/g
segments.push(match[0]) while match = re.exec(text)
segments

View File

@ -0,0 +1,165 @@
const _ = require('underscore-plus');
const CharacterPattern = new RegExp(/[^\s]/);
module.exports = {
activate() {
this.commandDisposable = atom.commands.add('atom-text-editor', {
'autoflow:reflow-selection': event => {
this.reflowSelection(event.currentTarget.getModel());
}
}
);
},
deactivate() {
this.commandDisposable?.dispose();
this.commandDisposable = null;
},
reflowSelection(editor) {
let range = editor.getSelectedBufferRange();
if (range.isEmpty()) { range = editor.getCurrentParagraphBufferRange(); }
if (range == null) { return; }
const reflowOptions = {
wrapColumn: this.getPreferredLineLength(editor),
tabLength: this.getTabLength(editor)
};
const reflowedText = this.reflow(editor.getTextInRange(range), reflowOptions);
return editor.getBuffer().setTextInRange(range, reflowedText);
},
reflow(text, {wrapColumn, tabLength}) {
let tabLengthInSpaces;
const paragraphs = [];
// Convert all \r\n and \r to \n. The text buffer will normalize them later
text = text.replace(/\r\n?/g, '\n');
let leadingVerticalSpace = text.match(/^\s*\n/);
if (leadingVerticalSpace) {
text = text.substr(leadingVerticalSpace.length);
} else {
leadingVerticalSpace = '';
}
let trailingVerticalSpace = text.match(/\n\s*$/);
if (trailingVerticalSpace) {
text = text.substr(0, text.length - trailingVerticalSpace.length);
} else {
trailingVerticalSpace = '';
}
const paragraphBlocks = text.split(/\n\s*\n/g);
if (tabLength) {
tabLengthInSpaces = Array(tabLength + 1).join(' ');
} else {
tabLengthInSpaces = '';
}
for (let block of paragraphBlocks) {
let blockLines = block.split('\n');
// For LaTeX tags surrounding the text, we simply ignore them, and
// reproduce them verbatim in the wrapped text.
const beginningLinesToIgnore = [];
const endingLinesToIgnore = [];
const latexTagRegex = /^\s*\\\w+(\[.*\])?\{\w+\}(\[.*\])?\s*$/g; // e.g. \begin{verbatim}
const latexTagStartRegex = /^\s*\\\w+\s*\{\s*$/g; // e.g. \item{
const latexTagEndRegex = /^\s*\}\s*$/g; // e.g. }
while ((blockLines.length > 0) && (
blockLines[0].match(latexTagRegex) ||
blockLines[0].match(latexTagStartRegex))) {
beginningLinesToIgnore.push(blockLines[0]);
blockLines.shift();
}
while ((blockLines.length > 0) && (
blockLines[blockLines.length - 1].match(latexTagRegex) ||
blockLines[blockLines.length - 1].match(latexTagEndRegex))) {
endingLinesToIgnore.unshift(blockLines[blockLines.length - 1]);
blockLines.pop();
}
// The paragraph might be a LaTeX section with no text, only tags:
// \documentclass{article}
// In that case, we have nothing to reflow.
// Push the tags verbatim and continue to the next paragraph.
if (!(blockLines.length > 0)) {
paragraphs.push(block);
continue;
}
// TODO: this could be more language specific. Use the actual comment char.
// Remember that `-` has to be the last character in the character class.
let linePrefix = blockLines[0].match(/^\s*(\/\/|\/\*|;;|#'|\|\|\||--|[#%*>-])?\s*/g)[0];
let linePrefixTabExpanded = linePrefix;
if (tabLengthInSpaces) {
linePrefixTabExpanded = linePrefix.replace(/\t/g, tabLengthInSpaces);
}
if (linePrefix) {
var escapedLinePrefix = _.escapeRegExp(linePrefix);
blockLines = blockLines.map(blockLine => blockLine.replace(new RegExp(`^${escapedLinePrefix}`), ''));
}
blockLines = blockLines.map(blockLine => blockLine.replace(/^\s+/, ''));
const lines = [];
let currentLine = [];
let currentLineLength = linePrefixTabExpanded.length;
const wrappedLinePrefix = linePrefix
.replace(/^(\s*)\/\*/, '$1 ')
.replace(/^(\s*)-(?!-)/, '$1 ');
let firstLine = true;
for (let segment of this.segmentText(blockLines.join(' '))) {
if (this.wrapSegment(segment, currentLineLength, wrapColumn)) {
// Independent of line prefix don't mess with it on the first line
if (firstLine !== true) {
// Handle C comments
if ((linePrefix.search(/^\s*\/\*/) !== -1) || (linePrefix.search(/^\s*-(?!-)/) !== -1)) {
linePrefix = wrappedLinePrefix;
}
}
lines.push(linePrefix + currentLine.join(''));
currentLine = [];
currentLineLength = linePrefixTabExpanded.length;
firstLine = false;
}
currentLine.push(segment);
currentLineLength += segment.length;
}
lines.push(linePrefix + currentLine.join(''));
const wrappedLines = beginningLinesToIgnore.concat(lines.concat(endingLinesToIgnore));
paragraphs.push(wrappedLines.join('\n').replace(/\s+\n/g, '\n'));
}
return leadingVerticalSpace + paragraphs.join('\n\n') + trailingVerticalSpace;
},
getTabLength(editor) {
return atom.config.get('editor.tabLength', { scope: editor.getRootScopeDescriptor() }) ?? 2;
},
getPreferredLineLength(editor) {
return atom.config.get('editor.preferredLineLength', {scope: editor.getRootScopeDescriptor()});
},
wrapSegment(segment, currentLineLength, wrapColumn) {
return CharacterPattern.test(segment) &&
((currentLineLength + segment.length) > wrapColumn) &&
((currentLineLength > 0) || (segment.length < wrapColumn));
},
segmentText(text) {
let match;
const segments = [];
const re = /[\s]+|[^\s]+/g;
while ((match = re.exec(text))) { segments.push(match[0]); }
return segments;
}
};

View File

@ -1,68 +0,0 @@
{CompositeDisposable, Disposable} = require 'atom'
_ = require 'underscore-plus'
Grim = require 'grim'
module.exports =
class DeprecationCopStatusBarView
lastLength: null
toolTipDisposable: null
constructor: ->
@subscriptions = new CompositeDisposable
@element = document.createElement('div')
@element.classList.add('deprecation-cop-status', 'inline-block', 'text-warning')
@element.setAttribute('tabindex', -1)
@icon = document.createElement('span')
@icon.classList.add('icon', 'icon-alert')
@element.appendChild(@icon)
@deprecationNumber = document.createElement('span')
@deprecationNumber.classList.add('deprecation-number')
@deprecationNumber.textContent = '0'
@element.appendChild(@deprecationNumber)
clickHandler = ->
workspaceElement = atom.views.getView(atom.workspace)
atom.commands.dispatch workspaceElement, 'deprecation-cop:view'
@element.addEventListener('click', clickHandler)
@subscriptions.add(new Disposable(=> @element.removeEventListener('click', clickHandler)))
@update()
debouncedUpdateDeprecatedSelectorCount = _.debounce(@update, 1000)
@subscriptions.add Grim.on 'updated', @update
# TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
if atom.styles.onDidUpdateDeprecations?
@subscriptions.add(atom.styles.onDidUpdateDeprecations(debouncedUpdateDeprecatedSelectorCount))
destroy: ->
@subscriptions.dispose()
@element.remove()
getDeprecatedCallCount: ->
Grim.getDeprecations().map((d) -> d.getStackCount()).reduce(((a, b) -> a + b), 0)
getDeprecatedStyleSheetsCount: ->
# TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
if atom.styles.getDeprecations?
Object.keys(atom.styles.getDeprecations()).length
else
0
update: =>
length = @getDeprecatedCallCount() + @getDeprecatedStyleSheetsCount()
return if @lastLength is length
@lastLength = length
@deprecationNumber.textContent = "#{_.pluralize(length, 'deprecation')}"
@toolTipDisposable?.dispose()
@toolTipDisposable = atom.tooltips.add @element, title: "#{_.pluralize(length, 'call')} to deprecated methods"
if length is 0
@element.style.display = 'none'
else
@element.style.display = ''

View File

@ -0,0 +1,104 @@
const { CompositeDisposable, Disposable } = require("atom");
const _ = require("underscore-plus");
const Grim = require("grim");
module.exports = class DeprecationCopStatusBarView {
static lastLength = null;
static toolTipDisposable = null;
constructor() {
this.update = this.update.bind(this);
this.subscriptions = new CompositeDisposable();
this.element = document.createElement("div");
this.element.classList.add(
"deprecation-cop-status",
"inline-block",
"text-warning"
);
this.element.setAttribute("tabindex", -1);
this.icon = document.createElement("span");
this.icon.classList.add("icon", "icon-alert");
this.element.appendChild(this.icon);
this.deprecationNumber = document.createElement("span");
this.deprecationNumber.classList.add("deprecation-number");
this.deprecationNumber.textContent = "0";
this.element.appendChild(this.deprecationNumber);
const clickHandler = function () {
const workspaceElement = atom.views.getView(atom.workspace);
atom.commands.dispatch(workspaceElement, "deprecation-cop:view");
};
this.element.addEventListener("click", clickHandler);
this.subscriptions.add(
new Disposable(() =>
this.element.removeEventListener("click", clickHandler)
)
);
this.update();
const debouncedUpdateDeprecatedSelectorCount = _.debounce(
this.update,
1000
);
this.subscriptions.add(Grim.on("updated", this.update));
// TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
if (atom.styles.onDidUpdateDeprecations != null) {
this.subscriptions.add(
atom.styles.onDidUpdateDeprecations(
debouncedUpdateDeprecatedSelectorCount
)
);
}
}
destroy() {
this.subscriptions.dispose();
this.element.remove();
}
getDeprecatedCallCount() {
return Grim.getDeprecations()
.map((d) => d.getStackCount())
.reduce((a, b) => a + b, 0);
}
getDeprecatedStyleSheetsCount() {
// TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
if (atom.styles.getDeprecations != null) {
return Object.keys(atom.styles.getDeprecations()).length;
} else {
return 0;
}
}
update() {
const length =
this.getDeprecatedCallCount() + this.getDeprecatedStyleSheetsCount();
if (this.lastLength === length) {
return;
}
this.lastLength = length;
this.deprecationNumber.textContent = `${_.pluralize(
length,
"deprecation"
)}`;
this.toolTipDisposable?.dispose();
this.toolTipDisposable = atom.tooltips.add(this.element, {
title: `${_.pluralize(length, "call")} to deprecated methods`,
});
if (length === 0) {
return (this.element.style.display = "none");
} else {
return (this.element.style.display = "");
}
}
};