Skip to content

Commit dfa49d0

Browse files
committed
feat: fix problems infrastructure and eslint fix error impl
1 parent 9690759 commit dfa49d0

7 files changed

Lines changed: 137 additions & 74 deletions

File tree

src/editor/Editor.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,7 @@ define(function (require, exports, module) {
11681168
* @param {Object} [options] - When given, it should be one of the predefined `Editor.MARK_OPTION_UNDERLINE*` or
11691169
* it should be an object that may contain the following configuration options:
11701170
*
1171+
* @param {string} [options.metadata] - If you want to store any metadata object with the mark, use this.
11711172
* @param {string} [options.className] -Assigns a CSS class to the marked stretch of text.
11721173
* @param {string} [options.css] -A string of CSS to be applied to the covered text. For example "color: #fe3".
11731174
* @param {string} [options.startStyle] -Can be used to specify an extra CSS class to be applied to the leftmost
@@ -1226,6 +1227,7 @@ define(function (require, exports, module) {
12261227
Editor.prototype.markText = function (markType, cursorFrom, cursorTo, options) {
12271228
let newMark = this._codeMirror.markText(cursorFrom, cursorTo, options);
12281229
newMark.markType = markType;
1230+
newMark.metadata = options && options.metadata;
12291231
return newMark;
12301232
};
12311233

src/extensions/default/JSLint/ESLint.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,25 @@ define(function (require, exports, module) {
102102

103103
function _getErrors(resultArray) {
104104
return resultArray.map(function (lintError) {
105+
let fix = null;
106+
if(lintError.fix && lintError.fix.range && typeof lintError.fix.text === "string") {
107+
fix = {
108+
replaceText: lintError.fix.text,
109+
rangeOffset: {
110+
start: lintError.fix.range[0],
111+
end: lintError.fix.range[1]
112+
}
113+
};
114+
}
105115
return {
106116
pos: { line: _0Based(lintError.line), ch: _0Based(lintError.column)},
107117
endPos: {
108118
line: _0Based(lintError.endLine, lintError.line),
109119
ch: _0Based(lintError.endColumn, lintError.column)
110120
},
111121
message: `${lintError.message} ESLint (${lintError.ruleId})`,
112-
type: _getErrorClass(lintError.severity)
122+
type: _getErrorClass(lintError.severity),
123+
fix: fix
113124
};
114125
});
115126
}

src/htmlContent/problems-panel-table.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
<input type="hidden" value="{{providerName}}" />
88
{{providerName}} ({{results.length}})
99
</td>
10+
<td></td>
11+
<td></td>
12+
<td></td>
13+
<td></td>
1014
</tr>
1115
{{#results}}
1216
<tr class="{{display}} inspector-line">
@@ -16,11 +20,19 @@
1620
</td>
1721
<td class="line-text">{{message}}{{{htmlMessage}}}</td>
1822
<td>
19-
<button class="btn btn-mini table-copy-err-button" title="{{Strings.COPY_ERROR}}"
23+
<button class="btn btn-mini table-copy-err-button ph-copy-problem" title="{{Strings.COPY_ERROR}}"
2024
data-message="{{message}}">
21-
<i class="fa-solid fa-copy"></i>
25+
<i class="fa-solid fa-copy ph-copy-problem"></i>
2226
</button>
2327
</td>
28+
<td>
29+
{{#fix.id}}
30+
<button class="btn btn-mini table-fix-err-button ph-fix-problem" title="{{Strings.FIX_ERROR}}"
31+
data-fixid="{{fix.id}}">
32+
{{Strings.FIX}}
33+
</button>
34+
{{/fix.id}}
35+
</td>
2436
<td class="line-snippet">{{codeSnippet}}</td>
2537
</tr>
2638
{{/results}}

src/language/CodeInspection.js

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ define(function (require, exports, module) {
4343
CommandManager = require("command/CommandManager"),
4444
DocumentManager = require("document/DocumentManager"),
4545
EditorManager = require("editor/EditorManager"),
46+
Dialogs = require("widgets/Dialogs"),
4647
Editor = require("editor/Editor").Editor,
4748
MainViewManager = require("view/MainViewManager"),
4849
LanguageManager = require("language/LanguageManager"),
@@ -61,6 +62,8 @@ define(function (require, exports, module) {
6162
const CODE_INSPECTION_GUTTER_PRIORITY = 500,
6263
CODE_INSPECTION_GUTTER = "code-inspection-gutter";
6364

65+
const EDIT_ORIGIN_LINT_FIX = "lint_fix";
66+
6467
const INDICATOR_ID = "status-inspection";
6568

6669
/** Values for problem's 'type' property */
@@ -504,20 +507,35 @@ define(function (require, exports, module) {
504507
});
505508
}
506509

510+
let fixIDCounter = 1;
511+
let documentFixes = new Map(), lastDocumentScanTimeStamp;
512+
function _registerNewFix(editor, fix) {
513+
if(!editor || !fix || !fix.rangeOffset) {
514+
return null;
515+
}
516+
if(editor.document.lastChangeTimestamp !== lastDocumentScanTimeStamp){
517+
// the document changed from the last time the fixes where registered, we have to
518+
// invalidate all existing fixes in that case.
519+
lastDocumentScanTimeStamp = editor.document.lastChangeTimestamp;
520+
documentFixes.clear();
521+
}
522+
fixIDCounter++;
523+
documentFixes.set(`${fixIDCounter}`, fix);
524+
return fixIDCounter;
525+
}
507526

508527
/**
509528
* Adds gutter icons and squiggly lines under err/warn/info to editor after lint.
529+
* also updates the passed in resultProviderEntries with fixes that can be applied.
510530
* @param resultProviderEntries
511531
* @private
512532
*/
513-
function _updateEditorMarks(resultProviderEntries) {
533+
function _updateEditorMarksAndFixResults(resultProviderEntries) {
514534
let editor = EditorManager.getCurrentFullEditor();
515535
if(!(editor && resultProviderEntries && resultProviderEntries.length)) {
516536
return;
517537
}
518538
editor.operation(function () {
519-
editor.clearAllMarks(CODE_MARK_TYPE_INSPECTOR);
520-
editor.clearGutter(CODE_INSPECTION_GUTTER);
521539
editor.off("viewportChange.codeInspection");
522540
editor.on("viewportChange.codeInspection", _editorVieportChangeHandler);
523541
let gutterErrorMessages = {};
@@ -532,11 +550,16 @@ define(function (require, exports, module) {
532550
// add squiggly lines
533551
if (_shouldMarkTokenAtPosition(editor, error)) {
534552
let mark;
553+
const markOptions = _getMarkOptions(error);
554+
const fixID = _registerNewFix(editor, error.fix);
555+
if(fixID) {
556+
markOptions.metadata = fixID;
557+
error.fix.id = fixID;
558+
}
535559
if(error.endPos){
536-
mark = editor.markText(CODE_MARK_TYPE_INSPECTOR, error.pos, error.endPos,
537-
_getMarkOptions(error));
560+
mark = editor.markText(CODE_MARK_TYPE_INSPECTOR, error.pos, error.endPos, markOptions);
538561
} else {
539-
mark = editor.markToken(CODE_MARK_TYPE_INSPECTOR, error.pos, _getMarkOptions(error));
562+
mark = editor.markToken(CODE_MARK_TYPE_INSPECTOR, error.pos, markOptions);
540563
}
541564
mark.type = error.type;
542565
mark.message = error.message;
@@ -547,6 +570,7 @@ define(function (require, exports, module) {
547570
});
548571
}
549572

573+
let linterHadRun = false;
550574
/**
551575
* Run inspector applicable to current document. Updates status bar indicator and refreshes error list in
552576
* bottom panel. Does not run if inspection is disabled or if a providerName is given and does not
@@ -574,6 +598,14 @@ define(function (require, exports, module) {
574598
return !provider.canInspect || provider.canInspect(currentDoc.file.fullPath);
575599
});
576600

601+
let editor = EditorManager.getCurrentFullEditor();
602+
if(editor){
603+
lastDocumentScanTimeStamp = editor.document.lastChangeTimestamp;
604+
documentFixes.clear();
605+
editor.clearAllMarks(CODE_MARK_TYPE_INSPECTOR);
606+
editor.clearGutter(CODE_INSPECTION_GUTTER);
607+
}
608+
577609
if (providerList && providerList.length) {
578610
var numProblems = 0;
579611
var aborted = false;
@@ -584,7 +616,7 @@ define(function (require, exports, module) {
584616

585617
// run all the providers registered for this file type
586618
(_currentPromise = inspectFile(currentDoc.file, providerList)).then(function (results) {
587-
_updateEditorMarks(results);
619+
_updateEditorMarksAndFixResults(results);
588620
// check if promise has not changed while inspectFile was running
589621
if (this !== _currentPromise) {
590622
return;
@@ -675,6 +707,7 @@ define(function (require, exports, module) {
675707
// No provider for current file
676708
_hasErrors = false;
677709
_currentPromise = null;
710+
updatePanelTitleAndStatusBar(0, [], false);
678711
if(problemsPanel){
679712
problemsPanel.hide();
680713
}
@@ -708,9 +741,18 @@ define(function (require, exports, module) {
708741
* @param {{name:string, scanFileAsync:?function(string, string):!{$.Promise},
709742
* scanFile:?function(string, string):?{errors:!Array, aborted:boolean}}} provider
710743
*
711-
* Each error is: { pos:{line,ch}, endPos:?{line,ch}, message:string, type:?Type }
744+
* Each error is: { pos:{line,ch}, endPos:?{line,ch}, message:string, htmlMessage:string, type:?Type ,
745+
* fix: { // an optional fix, if present will show the fix button
746+
* replace: "text to replace the offset given below",
747+
* rangeOffset: {
748+
* start: number,
749+
* end: number
750+
* }}}
712751
* If type is unspecified, Type.WARNING is assumed.
713752
* If no errors found, return either null or an object with a zero-length `errors` array.
753+
* `message` will be printed as text as is. This is needed when the error text contains HTML that may be
754+
* mis interpreted as html to display. If you want to display html, pass in `htmlMessage`. Both can be used
755+
* at the same time, in which case both will be displayed.
714756
*/
715757
function register(languageId, provider) {
716758
if (!_providers[languageId]) {
@@ -898,11 +940,35 @@ define(function (require, exports, module) {
898940
var $selectedRow;
899941
$problemsPanelTable = $problemsPanel.find(".table-container")
900942
.on("click", "tr", function (e) {
901-
if ($(e.target).hasClass('table-copy-err-button')) {
943+
if ($(e.target).hasClass('ph-copy-problem')) {
902944
// Retrieve the message from the data attribute of the clicked element
903945
const message = $(e.target).parent().parent().find(".line-text").text();
904946
message && Phoenix.app.copyToClipboard(message);
947+
e.preventDefault();
948+
e.stopPropagation();
949+
MainViewManager.focusActivePane();
950+
return;
951+
}
952+
if ($(e.target).hasClass('ph-fix-problem')) {
953+
// Retrieve the message from the data attribute of the clicked element
954+
const fixid = "" + $(e.target).data("fixid");
955+
const fixDetails = documentFixes.get(fixid);
956+
const editor = EditorManager.getCurrentFullEditor();
957+
if(!editor || !fixDetails || editor.document.lastChangeTimestamp !== lastDocumentScanTimeStamp) {
958+
Dialogs.showErrorDialog(Strings.CANNOT_FIX_TITLE, Strings.CANNOT_FIX_MESSAGE);
959+
} else {
960+
const from = editor.posFromIndex(fixDetails.rangeOffset.start),
961+
to = editor.posFromIndex(fixDetails.rangeOffset.end);
962+
editor.setSelection(from, to, true, Editor.BOUNDARY_BULLSEYE, EDIT_ORIGIN_LINT_FIX);
963+
editor.replaceSelection(fixDetails.replaceText, "around");
964+
}
965+
e.preventDefault();
966+
e.stopPropagation();
967+
MainViewManager.focusActivePane();
968+
run();
969+
return;
905970
}
971+
906972
if ($selectedRow) {
907973
$selectedRow.removeClass("selected");
908974
}

src/nls/root/strings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@ define({
418418
"NO_LINT_AVAILABLE": "No linter available for {0}",
419419
"NOTHING_TO_LINT": "Nothing to lint",
420420
"COPY_ERROR": "Copy Error Message",
421+
"FIX_ERROR": "Fix Problem",
422+
"FIX": "Fix",
423+
"CANNOT_FIX_TITLE": "Failed to Apply Fix",
424+
"CANNOT_FIX_MESSAGE": "The document has been modified since the fix was prepared. Please try again.",
421425
"LINTER_TIMED_OUT": "{0} has timed out after waiting for {1} ms",
422426
"LINTER_FAILED": "{0} terminated with error: {1}",
423427

src/styles/brackets.less

Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -160,73 +160,42 @@ html, body {
160160
// selectors like .warning or .info. Even if error, warning, and info are used on the same div in any order,
161161
// the style for .error.error will take precedence due to its higher specificity.
162162
.editor-text-fragment-error.editor-text-fragment-error.editor-text-fragment-error.editor-text-fragment-error{
163-
border-bottom:2px dotted @error-underline-text;
164-
display: inline-block;
165-
position: relative;
166-
}
167-
168-
.editor-text-fragment-error.editor-text-fragment-error.editor-text-fragment-error.editor-text-fragment-error:after{
169-
content: '';
170-
width: 100%;
171-
border-bottom:2px dotted @error-underline-text;
172-
position: absolute;
173-
display:block;
174-
bottom: -3px;
175-
left: 2px;
176-
}
177-
178-
.editor-text-fragment-warn.editor-text-fragment-warn.editor-text-fragment-warn{
179-
border-bottom:2px dotted @warn-underline-text;
180-
.dark &{
181-
border-bottom:2px dotted @dark-warn-underline-text;
182-
}
183-
display: inline-block;
184-
position: relative;
163+
text-decoration-style: wavy;
164+
text-decoration-line: underline;
165+
text-decoration-skip-ink: none;
166+
text-decoration-thickness: 1px;
167+
text-underline-offset: 2px;
168+
text-decoration-color: @error-underline-text;
185169
}
186170

187-
.editor-text-fragment-warn.editor-text-fragment-warn.editor-text-fragment-warn:after{
188-
content: '';
189-
width: 100%;
190-
border-bottom:2px dotted @warn-underline-text;
171+
.editor-text-fragment-warn.editor-text-fragment-warn.editor-text-fragment-warn {
172+
text-decoration-style: wavy;
173+
text-decoration-line: underline;
174+
text-decoration-skip-ink: none;
175+
text-decoration-thickness: 1px;
176+
text-underline-offset: 2px;
177+
text-decoration-color: @warn-underline-text;
191178
.dark &{
192-
border-bottom:2px dotted @dark-warn-underline-text;
179+
text-decoration-color: @dark-warn-underline-text;
193180
}
194-
position: absolute;
195-
display:block;
196-
bottom: -3px;
197-
left: 2px;
198181
}
199182

200-
.editor-text-fragment-info{
201-
border-bottom:2px dotted @info-underline-text;
202-
display: inline-block;
203-
position: relative;
204-
}
205-
206-
.editor-text-fragment-info:after{
207-
content: '';
208-
width: 100%;
209-
border-bottom:2px dotted @info-underline-text;
210-
position: absolute;
211-
display:block;
212-
bottom: -3px;
213-
left: 2px;
214-
}
215-
216-
.editor-text-fragment-spell-error.editor-text-fragment-spell-error{
217-
border-bottom:2px dotted @spell-underline-text;
218-
display: inline-block;
219-
position: relative;
183+
.editor-text-fragment-spell-error.editor-text-fragment-spell-error {
184+
text-decoration-style: wavy;
185+
text-decoration-line: underline;
186+
text-decoration-skip-ink: none;
187+
text-decoration-thickness: 1px;
188+
text-underline-offset: 2px;
189+
text-decoration-color: @spell-underline-text;
220190
}
221191

222-
.editor-text-fragment-spell-error.editor-text-fragment-spell-error:after{
223-
content: '';
224-
width: 100%;
225-
border-bottom:2px dotted @spell-underline-text;
226-
position: absolute;
227-
display:block;
228-
bottom: -3px;
229-
left: 2px;
192+
.editor-text-fragment-info {
193+
text-decoration-style: wavy;
194+
text-decoration-line: underline;
195+
text-decoration-skip-ink: none;
196+
text-decoration-thickness: 1px;
197+
text-underline-offset: 2px;
198+
text-decoration-color: @info-underline-text;
230199
}
231200

232201
.hidden-element{
@@ -3005,7 +2974,6 @@ textarea.exclusions-editor {
30052974

30062975
.table-copy-err-button {
30072976
visibility: hidden;
3008-
cursor: pointer;
30092977
margin: 0 0 0 0;
30102978
}
30112979

src/styles/brackets_patterns_override.less

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,8 +1940,8 @@ input[type="color"],
19401940
}
19411941
}
19421942

1943-
// Update Button Type
1944-
&.update {
1943+
// Safe(Green) or Update Button Type
1944+
&.safe, &.update {
19451945
background-color: @bc-secondary-btn-bg;
19461946
border-color: @bc-secondary-btn-border;
19471947
box-shadow: inset 0 1px 0 @bc-highlight;

0 commit comments

Comments
 (0)