Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(language-core): support overlapping ranges #205

Closed
wants to merge 1 commit into from

Conversation

piotrtomiak
Copy link
Contributor

Closes #203

@@ -60,13 +60,13 @@ export class SourceMapWithDocuments {
}

public * getSourcePositions(position: vscode.Position, filter: (data: CodeInformation) => boolean = () => true) {
for (const mapped of this.findPositions(position, filter, this.embeddedDocument, this.sourceDocument, 'generatedOffsets', 'sourceOffsets')) {
for (const mapped of this.findPositions(position, filter, this.embeddedDocument, this.sourceDocument, 'generatedOffsets')) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've cleaned up an API a little bit - 'sourceOffsets' is a duplciated information here

) {
for (const mapped of this.map.findMatching(fromDoc.offsetAt(position), from, to)) {
for (const mapped of this.map.findMatchingOffsets(fromDoc.offsetAt(position), from)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored name for consistency with other methods on the map

if (filter(mapping.data)) {
mappedStart = mapped;
break;
for (const [mappedStart, mappedEnd] of map.getGeneratedStartEnd(start, end, filter)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filter is added to map API, to optimize iterations and disregard mappings early on

const isSorted = fromOffsets.every((value, index) => index === 0 || fromOffsets[index - 1] <= value);
if (!isSorted) {
throw new Error('fromOffsets must be sorted in ascending order');
if (!areRangesSortedAndNonOverlapping(fromOffsets, fromLengths)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added stricter check - ranges should not be overlapping with the mapping

test('Angular template with overlapping ranges', () => {
const map = new SourceMap([
{
sourceOffsets: [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is where our misunderstanding comes from. When generating the map, I am putting all of the mappings into a single offsets array. It seems that this is not the intended use of the API, but it's a more performative one - less memory consumption.

}
}
if (!mapped) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the logic goes to the map, which can fallback to map-anything-mode

const memo = this.getMemoBasedOnRange(fromRange);
if (memo.offsets.length === 0) {
return;
* findMatchingStartEnd(start: number, end: number, fromRange: CodeRangeKey, filter?: (data: Data) => boolean): Generator<[start: number, end: number, Mapping<Data>]> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving range matching logic to the source map ensures that we have consistency across the codebase

if (mapped !== undefined) {
yield [mapped, mapping as Mapping<Data>] as const;
for (const endMatch of endMatches) {
if (endMatch[0] != mapping) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this part, maybe we can have a second loop as a fallback, where we try to match just anything

}

private createStorage(key: CodeRangeKey): MapStorage<Data> {
if (this.mappings.every(mapping => areRangesSortedAndNonOverlapping(mapping[key], getLengths(mapping, key)))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several thoughts here:

  1. We could also just throw an error if mappings are too large or contain overlapping ranges and not use IntervalTreeStorage at all.
  2. I wonder if there are any benefits to use IntervalTreeStorage in terms of performance.
  3. It seems that we small modification, your structure can be reused for overlapping ranges as well. Which structure would be more performant?
  4. The thing is, that if you have many mappings, storing their offsets and lengths in arrays takes much less memory as you don't need to create an object for each mapping - see the modified Angular template test how I use mappings.

const fromLength = fromLengths[mid];

if (offset >= fromOffset && offset <= fromOffset + fromLength) {
yield [mapping, mid]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference in the algorithm here is that I return the index instead of ready-to-use offset, as I can than match ranges, so one would not need to do zero-length thingies.

@piotrtomiak
Copy link
Contributor Author

I'll create a new one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support overlapping mapping ranges
1 participant