Skip to content

Add cursorOffset option for cursor translation #1637

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

Merged
merged 10 commits into from
Jun 1, 2017

Conversation

josephfrazier
Copy link
Collaborator

This addresses #93 by
adding a new option, cursorOffset, that tells prettier to determine
the location of the cursor after the code has been formatted. This is
accessible through the API via a new function, formatWithCursor, which
returns a {formatted: string, cursorOffset: ?number}.

Here's a usage example:

require('prettier').formatWithCursor(' 1', {cursorOffset: 1})
// -> { formatted: '1;\n', cursorOffset: 0 }

This implementation works roughly as described in #93 (comment).
It doesn't yet have tests, but I wanted to go ahead and get some
feedback on the API to see if this seems like the right way to go.

Thoughts?

@vjeux
Copy link
Contributor

vjeux commented May 20, 2017

Omg this is so exciting! I'll review this when I get a chance

@CiGit
Copy link
Member

CiGit commented May 20, 2017

Shouldn't a cursorOffset be a tuple [line, column]?
Would line/column be 0 or 1 based ?

@josephfrazier
Copy link
Collaborator Author

Thanks @vjeux, I'm looking forward to the feedback!

@CiGit, since the input text is a single string, rather than an array of lines, I decided to use a single zero-based integer as the cursorOffset. A [line, column] tuple could also work, but it would have to be converted back into a single integer anyway. Besides, I'm guessing editors integrating with this feature already keep track of the single-integer offset, or can easily generate it given the [line, column].

index.js Outdated
@@ -1,5 +1,8 @@
"use strict";

const traverse = require('babel-traverse').default;
Copy link
Contributor

Choose a reason for hiding this comment

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

does it work for flow AST?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, it didn't, but I added support in cbc1929

@vjeux
Copy link
Contributor

vjeux commented May 21, 2017

Sorry I don't have the time to review it right now, I'll take another pass later.

josephfrazier added a commit to josephfrazier/prettier that referenced this pull request May 21, 2017
See prettier#1637 (comment)

For example:

    prettier tests/flow/any/any.js --cursor-offset 40 --parser flow
    # prints 44
@josephfrazier
Copy link
Collaborator Author

josephfrazier commented May 21, 2017

No worries, I still have to refactor this to make it work with the range formatting changes (#1609) anyway.

@vjeux
Copy link
Contributor

vjeux commented May 24, 2017

Should I wait for you to refactor it based on the findings in the other diff? I'd love to get this feature in!

@josephfrazier
Copy link
Collaborator Author

josephfrazier commented May 24, 2017

I haven't done any further work on this yet, so if you'd like to, feel free! The range-formatting feature does make it more complex, though, since this change introduces a separate formatWithCursor function and moves the shebang handling into it.

Also note that if a range is specified the cursor could technically be inside or outside of it, rather than just on one of its endpoints, but that's not likely to happen in practice, so we can probably ignore those cases for now.

EDIT: Now that I think about it, range-formatting probably doesn't currently take shebang handling into account, meaning that the formatted range will be offset by the shebang length...

josephfrazier added a commit to josephfrazier/prettier that referenced this pull request May 24, 2017
That way, we won't have to do any arithmetic on range and cursor
locations when there is a shebang. See here for details:

prettier#1637 (comment)

This required changing comment printing such that comments that are
actually shebangs are just ignored.
josephfrazier added a commit that referenced this pull request May 24, 2017
* Verify shebang tests against Babylon as well as Flow/Typescript

* Pass entire text through formatWithShebang() to format()

That way, we won't have to do any arithmetic on range and cursor
locations when there is a shebang. See here for details:

#1637 (comment)

This required changing comment printing such that comments that are
actually shebangs are just ignored.

* Add curly brace after `if`

See #1718 (comment)
@vjeux vjeux changed the title (WIP) Add cursorOffset option for cursor translation [WIP] Add cursorOffset option for cursor translation May 25, 2017
This addresses prettier#93 by
adding a new option, `cursorOffset`, that tells prettier to determine
the location of the cursor after the code has been formatted. This is
accessible through the API via a new function, `formatWithCursor`, which
returns a `{formatted: string, cursorOffset: ?number}`.

Here's a usage example:

```js
require("prettier").formatWithCursor(" 1", { cursorOffset: 2 });
// -> { formatted: '1;\n', cursorOffset: 1 }
```
It will print out the offset instead of the formatted output. This
makes it easier to test. For example:

    echo ' 1' | prettier --stdin --cursor-offset 2
    # prints 1
addAlignmentSize = addAlignmentSize || 0;

const ast = parser.parse(text, opts);

const formattedRangeOnly = formatRange(text, opts, ast);
if (formattedRangeOnly) {
return formattedRangeOnly;
return { formatted: formattedRangeOnly };
}
Copy link
Collaborator Author

@josephfrazier josephfrazier May 30, 2017

Choose a reason for hiding this comment

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

The cursor-translation functionality still needs to be unified with the range-formatting functionality (currently, either one or the other can be used). I think it might be easiest to do this by keeping track of the range start/end positions in the same way as the cursor position, then having an API that returns all 3 positions (if provided), and only formats the text necessary to satisfy the range.

@josephfrazier
Copy link
Collaborator Author

@vjeux, is there any chance this can be merged and land in tomorrow's release? It definitely still needs some work (as mentioned in #1637 (comment)), but releasing it would allow editor integrations to start building upon it.

src/printer.js Outdated
path,
p => genericPrint(p, options, printGenerically, args),
options,
args && args.needsSemi
);

if (path.getNode() === options.cursorNode) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You probably want to put this inside of printComments, there are a few places where we don't print the node but do print the comments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, that hadn't occurred to me. Fixed in d75906d

bin/prettier.js Outdated
}
});
}

function writeOutput(result) {
if (options.cursorOffset) {
console.log(result.cursorOffset);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we just output the cursor offset from CLI? We don't output the formatted text? Could we print the formatted text on stdout and the cursor on stderr?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, good idea, I hadn't thought of that. Fixed in 8f72d51

@vjeux
Copy link
Contributor

vjeux commented Jun 1, 2017

Okay let's ship it tomorrow. I just asked a few questions.

@@ -202,7 +204,7 @@ function format(input, opt) {
return;
}

return prettier.format(input, opt);
return prettier.formatWithCursor(input, opt);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm really upset that we have to introduce a new api and didn't think about this when we built .format :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

:/ It's a somewhat unwieldy name, too

@@ -4,6 +4,7 @@ const validate = require("jest-validate").validate;
const deprecatedConfig = require("./deprecated");

const defaults = {
cursorOffset: -1,
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't it be undefined here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, undefined would make more sense. However, when I tried it, I got a validation error:

 FAIL  tests/cursor/jsfmt.spec.js
  ● translates cursor correctly in basic case

    Error
      ● Validation Error:

        Option "cursorOffset" must be of type:
          undefined
        but instead received:
          number

        Example:
        {
          "cursorOffset": undefined
        }


  ✕ translates cursor correctly in basic case (3ms)

Here's the diff I tested:

diff --git a/index.js b/index.js
index 5a66c9cb..e9e67231 100644
--- a/index.js
+++ b/index.js
@@ -64,7 +64,7 @@ function formatWithCursor(text, opts, addAlignmentSize) {
   }
 
   let cursorOffset;
-  if (opts.cursorOffset >= 0) {
+  if (opts.cursorOffset !== undefined) {
     const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset);
     const cursorNode = cursorNodeAndParents.node;
     if (cursorNode) {
diff --git a/src/options.js b/src/options.js
index 73d40426..956168af 100644
--- a/src/options.js
+++ b/src/options.js
@@ -4,7 +4,7 @@ const validate = require("jest-validate").validate;
 const deprecatedConfig = require("./deprecated");
 
 const defaults = {
-  cursorOffset: -1,
+  cursorOffset: undefined,
   rangeStart: 0,
   rangeEnd: Infinity,
   useTabs: false,

Is there a good way to get around this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohhh, okay. Can you not put it in defaults? Or somehow make it optional there?

Copy link
Contributor

Choose a reason for hiding this comment

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

Otherwise don't worry, it's fine having -1

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I could remove it from defaults, but then there's a validation warning:

  console.warn node_modules/jest-validate/build/utils.js:43
    ● Validation Warning:

      Unknown option "cursorOffset" with value 2 was found.
      This is probably a typing mistake. Fixing it will remove this message.

Not sure if there's a way to make it optional, so I'll leave it as-is for now. We can always revisit it later.

}

let cursorOffset;
if (opts.cursorOffset >= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

!== undefined

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(for future reference, the discussion about this is above)

const cursorNode = cursorNodeAndParents.node;
if (cursorNode) {
cursorOffset = opts.cursorOffset - util.locStart(cursorNode);
opts.cursorNode = cursorNode;
Copy link
Contributor

Choose a reason for hiding this comment

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

don't mutate please

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, we're already mutating elsewhere with opts.newLine = guessLineEnding(text); and opts.originalText = text.trimRight();. I can take a look refactoring it tomorrow though, if you're set on this.

Copy link
Contributor

Choose a reason for hiding this comment

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

So sad. Would be nice to refactor but I can live with this for this PR.

@@ -411,6 +416,16 @@ function printDocToString(doc, options) {
}
}
}

const cursorPlaceholderIndex = out.indexOf(cursor.placeholder);
if (cursorPlaceholderIndex >= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

!== -1

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed in c4e08d6

const cursorPlaceholderIndex = out.indexOf(cursor.placeholder);
if (cursorPlaceholderIndex >= 0) {
const beforeCursor = out.slice(0, cursorPlaceholderIndex).join("");
options.cursorOffsetResult = beforeCursor.length;
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't mutate. Change the API such that it returns {formatted, cursor}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's much cleaner :) Fixed in 1fa6b4e

@josephfrazier josephfrazier changed the title [WIP] Add cursorOffset option for cursor translation Add cursorOffset option for cursor translation Jun 1, 2017
@vjeux
Copy link
Contributor

vjeux commented Jun 1, 2017

Is it ready to review again?

@josephfrazier
Copy link
Collaborator Author

Yup, I'll follow up on any feedback tomorrow.

@vjeux vjeux merged commit 4bfeb90 into prettier:master Jun 1, 2017
@vjeux
Copy link
Contributor

vjeux commented Jun 1, 2017

Thanks for this! It's going to be awesome <3

@josephfrazier josephfrazier deleted the cursor branch June 2, 2017 07:39
@josephfrazier
Copy link
Collaborator Author

Thanks for merging on such short notice before the release! I'll create a new issue for the task in #1637 (comment).

josephfrazier added a commit to josephfrazier/prettier that referenced this pull request Jun 5, 2017
Since the `cursorOffset` option (introduced in prettier#1637) works by tracking
the cursor position relative to an AST node (rather than a CST token),
it can produce incorrect results.

See prettier#1981
josephfrazier added a commit to josephfrazier/prettier that referenced this pull request Jun 5, 2017
Since the `cursorOffset` option (introduced in prettier#1637) works by tracking
the cursor position relative to an AST node (rather than a CST token),
it can produce incorrect results.

See prettier#1981
vjeux pushed a commit that referenced this pull request Jun 6, 2017
* Add test demonstrating SourceElement-relative cursor translation

See #1981 (comment)

* Translate cursor relative to nearest node, not SourceElement

This partially address #1981

See #1981 (comment)

* Add test demonstrating incorrect cursor translation

Since the `cursorOffset` option (introduced in #1637) works by tracking
the cursor position relative to an AST node (rather than a CST token),
it can produce incorrect results.

See #1981
@lock lock bot added the locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting. label Jan 20, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants