From 359509901e3b246f8d2c4b0221045a594145cc89 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 15 Jul 2022 15:30:15 -0400 Subject: [PATCH] Support codeactions at the edges of tokens. --- src/Program.ts | 2 +- .../codeActions/CodeActionsProcessor.spec.ts | 20 ++++ src/util.spec.ts | 106 ++++++++++++++++++ src/util.ts | 32 +++++- 4 files changed, 158 insertions(+), 2 deletions(-) diff --git a/src/Program.ts b/src/Program.ts index 4935e8bcf..c6bd08b9e 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -935,7 +935,7 @@ export class Program { //only keep diagnostics related to this file .filter(x => x.file === file) //only keep diagnostics that touch this range - .filter(x => util.rangesIntersect(x.range, range)); + .filter(x => util.rangesIntersectOrTouch(x.range, range)); const scopes = this.getScopesForFile(file); diff --git a/src/bscPlugin/codeActions/CodeActionsProcessor.spec.ts b/src/bscPlugin/codeActions/CodeActionsProcessor.spec.ts index 039a700d5..1019f8f01 100644 --- a/src/bscPlugin/codeActions/CodeActionsProcessor.spec.ts +++ b/src/bscPlugin/codeActions/CodeActionsProcessor.spec.ts @@ -295,4 +295,24 @@ describe('CodeActionsProcessor', () => { testGetCodeActions(file, util.createRange(3, 34, 3, 34), [`import "pkg:/source/second.bs"`]); }); }); + + it('suggests imports at very start and very end of diagnostic', () => { + program.setFile('source/first.bs', ` + namespace alpha + function firstAction() + end function + end namespace + `); + program.setFile('components/MainScene.xml', trim``); + const file = program.setFile('components/MainScene.bs', ` + sub init() + print alpha.firstAction() + end sub + `); + + // print |alpha.firstAction() + testGetCodeActions(file, util.createRange(2, 22, 2, 22), [`import "pkg:/source/first.bs"`]); + // print alpha|.firstAction() + testGetCodeActions(file, util.createRange(2, 27, 2, 27), [`import "pkg:/source/first.bs"`]); + }); }); diff --git a/src/util.spec.ts b/src/util.spec.ts index 7896cb1da..8f7d22504 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -534,6 +534,22 @@ describe('util', () => { }); describe('rangesIntersect', () => { + it('does not match when ranges do not touch (a < b)', () => { + // AA BB + expect(util.rangesIntersectOrTouch( + util.createRange(0, 0, 0, 1), + util.createRange(0, 2, 0, 3) + )).to.be.false; + }); + + it('does not match when ranges do not touch (a < b)', () => { + // BB AA + expect(util.rangesIntersectOrTouch( + util.createRange(0, 2, 0, 3), + util.createRange(0, 0, 0, 1) + )).to.be.false; + }); + it('does not match when ranges touch at right edge', () => { // AABB expect(util.rangesIntersect( @@ -607,6 +623,96 @@ describe('util', () => { }); }); + describe('rangesIntersectOrTouch', () => { + it('does not match when ranges do not touch (a < b)', () => { + // AA BB + expect(util.rangesIntersectOrTouch( + util.createRange(0, 0, 0, 1), + util.createRange(0, 2, 0, 3) + )).to.be.false; + }); + + it('does not match when ranges do not touch (a < b)', () => { + // BB AA + expect(util.rangesIntersectOrTouch( + util.createRange(0, 2, 0, 3), + util.createRange(0, 0, 0, 1) + )).to.be.false; + }); + + it('matches when ranges touch at right edge', () => { + // AABB + expect(util.rangesIntersectOrTouch( + util.createRange(0, 0, 0, 1), + util.createRange(0, 1, 0, 2) + )).to.be.true; + }); + + it('matches when ranges touch at left edge', () => { + // BBAA + expect(util.rangesIntersectOrTouch( + util.createRange(0, 1, 0, 2), + util.createRange(0, 0, 0, 1) + )).to.be.true; + }); + + it('matches when range overlaps by single character on the right', () => { + // A BA B + expect(util.rangesIntersectOrTouch( + util.createRange(0, 1, 0, 3), + util.createRange(0, 2, 0, 4) + )).to.be.true; + }); + + it('matches when range overlaps by single character on the left', () => { + // B AB A + expect(util.rangesIntersectOrTouch( + util.createRange(0, 2, 0, 4), + util.createRange(0, 1, 0, 3) + )).to.be.true; + }); + + it('matches when A is contained by B at the edges', () => { + // B AA B + expect(util.rangesIntersectOrTouch( + util.createRange(0, 2, 0, 3), + util.createRange(0, 1, 0, 4) + )).to.be.true; + }); + + it('matches when B is contained by A at the edges', () => { + // A BB A + expect(util.rangesIntersectOrTouch( + util.createRange(0, 1, 0, 4), + util.createRange(0, 2, 0, 3) + )).to.be.true; + }); + + it('matches when A and B are identical', () => { + // ABBA + expect(util.rangesIntersectOrTouch( + util.createRange(0, 1, 0, 2), + util.createRange(0, 1, 0, 2) + )).to.be.true; + }); + + it('matches when A spans multiple lines', () => { + // ABBA + expect(util.rangesIntersectOrTouch( + util.createRange(0, 1, 2, 0), + util.createRange(0, 1, 0, 3) + )).to.be.true; + }); + + it('matches when B spans multiple lines', () => { + // ABBA + expect(util.rangesIntersectOrTouch( + util.createRange(0, 1, 0, 3), + util.createRange(0, 1, 2, 0) + )).to.be.true; + }); + }); + it('sortByRange', () => { const front = { range: util.createRange(1, 1, 1, 2) diff --git a/src/util.ts b/src/util.ts index a13207e14..c65fd756b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -494,7 +494,13 @@ export class Util { } /** - * Does a touch b in any way? + * Do `a` and `b` overlap by at least one character. This returns false if they are at the edges. Here's some examples: + * ``` + * | true | true | true | true | true | false | false | false | false | + * |------|------|------|------|------|-------|-------|-------|-------| + * | aa | aaa | aaa | aaa | a | aa | aa | a | a | + * | bbb | bb | bbb | b | bbb | bb | bb | b | a | + * ``` */ public rangesIntersect(a: Range, b: Range) { // Check if `a` is before `b` @@ -511,6 +517,30 @@ export class Util { return true; } + /** + * Do `a` and `b` overlap by at least one character or touch at the edges + * ``` + * | true | true | true | true | true | true | true | false | false | + * |------|------|------|------|------|-------|-------|-------|-------| + * | aa | aaa | aaa | aaa | a | aa | aa | a | a | + * | bbb | bb | bbb | b | bbb | bb | bb | b | a | + * ``` + */ + public rangesIntersectOrTouch(a: Range, b: Range) { + // Check if `a` is before `b` + if (a.end.line < b.start.line || (a.end.line === b.start.line && a.end.character < b.start.character)) { + return false; + } + + // Check if `b` is before `a` + if (b.end.line < a.start.line || (b.end.line === a.start.line && b.end.character < a.start.character)) { + return false; + } + + // These ranges must intersect + return true; + } + /** * Test if `position` is in `range`. If the position is at the edges, will return true. * Adapted from core vscode