Mert Özen Further With Every Line
Artificial Intelligence GitHub Copilot — #02

Inline Suggestions and Next Edit Suggestions: Using Completions Well and When to Turn Them Off

Mert Özen Nov 15, 2026 9 min 5 views
Inline Suggestions and Next Edit Suggestions: Using Completions Well and When to Turn Them Off

Take inline suggestions with word and line accept after reading them, not with Tab, and never leave a test's expected value to completion: in Bookstore a test that repeated the formula showed correct code as red, and bending the code to fit it broke older tests with hand-picked values. Shortcuts, NES, context and turning completion off per language.

Version note: The code in this post was built and run with .NET 10, Entity Framework Core 10.0.15 and xUnit.net v3 4.0.3. The behavior of inline suggestions and next edit suggestions is taken from the Visual Studio 2026 (18.12) and Visual Studio Code (VS Code) 1.146 documentation as of 15 November 2026; no Copilot session was opened for this post, and none of the code shown is a Copilot suggestion.

Guess First

Ayşe is writing a test for the order line total in Bookstore, the online bookstore used as the shared example across this site's series. She types the method signature, presses Enter and accepts the body that appears as grey text with Tab. The expected value is produced with the "obvious" calculation of a line total:

decimal expected = Math.Round(price * quantity, 2);

Assert.Equal(expected, OrderItem.CalculateLineTotal(price, quantity));

The test is red. What is your guess? Is the bug in the code or in the test? And what happens if we "fix" the code so that the test turns green? We will see both in this post with real test output; but first, let us look at how completion works.

Accepting Ghost Text: All of It, Word by Word, Line by Line

An inline suggestion appears at the cursor as grey, italic text. Visual Studio 2026 shows it with syntax highlighting but at low opacity, so it stays distinct from your own code. Tab is not the only way to take a suggestion; most of the time the best use is to take part of it:

TaskVisual Studio 2026VS Code
Accept allTabTab
RejectEsc or keep typingEscape
Accept word by wordCtrl+Right arrowCtrl+Right arrow
Accept line by lineCtrl+Down arrow"Accept Next Line Of Inline Suggestion" in the Command Palette
See alternativesAlt+. (next), Alt+, (previous)The toolbar that appears when you hover over the suggestion
Trigger manuallyAlt+. or Alt+,The "Trigger Inline Suggestion" command

Visual Studio also has partial accept by clicking: as you move the mouse over the suggestion, the part that will be accepted is highlighted, and the part up to where you click is taken. While the IntelliSense list is open, Copilot suggestions are held back; they come back when you close the list. The value of word and line accept is this: a suggestion is usually right in its first half and turns into guessing towards the end. Taking the first line and writing the rest yourself is both faster and safer than accepting a half-wrong block and fixing it later.

Next Edit Suggestions: The Rest of the Change

Next edit suggestions (NES) answer a different question from ghost text. Ghost text asks "what goes at the cursor?"; NES asks "if you made this change, where else do you need to change?". In VS Code, when an NES suggestion appears, Tab takes you to where the suggestion is, a second Tab accepts it and Escape rejects it. The Visual Studio documentation also describes NES as a kind of inline suggestion that predicts both the location and the content of your next edit from your editing pattern.

Where it helps most is refactoring chains. In this post's Bookstore change we added an Institutional module for institutional customers and later added a Code property to the Institution entity. The rest of that single change lives in three places: HasMaxLength and a unique index in the configuration, a code for every record in the seed data, and a new field in the record the endpoint returns. These are exactly the kind of repeated edits, in the same file or nearby files, that NES tries to predict. In VS Code the github.copilot.nextEditSuggestions.fixes setting also lets NES make suggestions from compiler diagnostics (such as a missing using).

NES has a cost: its suggestion sometimes appears far from the cursor and pulls your attention away. The editor.inlineSuggest.edits.showCollapsed setting keeps the suggestion collapsed (only a marker in the gutter) until you go there. We added this setting to the repo at the end of this post.

What Determines Suggestion Quality

Inline suggestions look at the code around the cursor and at the files open in the editor; the VS Code documentation says that having related files open gives Copilot a bigger picture of your project. So the quality of a suggestion is largely in your hands: which files are open, how clear the names are, a comment that states the intent.

In Bookstore the clearest example is the Entity Framework Core (EF Core) configurations. Every module's configuration follows the same pattern: a table in its own schema, HasMaxLength for every string, HasConversion<string>() for enums, HasData for seed data. While writing the Institutional module's configuration, keeping CatalogConfiguration.cs and OrderingConfiguration.cs open makes it more likely that completion follows this pattern. The result was this file:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Bookstore.Institutional;

internal sealed class InstitutionConfiguration : IEntityTypeConfiguration<Institution>
{
    public void Configure(EntityTypeBuilder<Institution> institution)
    {
        institution.ToTable("Institutions", "institutional");
        institution.Property(i => i.Name).HasMaxLength(200);
        institution.Property(i => i.Code).HasMaxLength(20).IsUnicode(false);
        institution.HasIndex(i => i.Code).IsUnique();
        institution.Property(i => i.Kind).HasConversion<string>().HasMaxLength(20);
        institution.HasMany(i => i.Units).WithOne().HasForeignKey(u => u.InstitutionId).OnDelete(DeleteBehavior.Cascade);
        institution.HasData(InstitutionalSeed.Institutions);
    }
}

internal sealed class InstitutionUnitConfiguration : IEntityTypeConfiguration<InstitutionUnit>
{
    public void Configure(EntityTypeBuilder<InstitutionUnit> unit)
    {
        unit.ToTable("InstitutionUnits", "institutional");
        unit.Property(u => u.Name).HasMaxLength(200);
        unit.HasOne(u => u.ParentUnit).WithMany().HasForeignKey(u => u.ParentUnitId).OnDelete(DeleteBehavior.Restrict);
        unit.HasData(InstitutionalSeed.Units);
    }
}

The first five lines of this file can be predicted from the neighboring files; completion does a good job here. The lines that need a real decision are the ones outside the pattern: why Code is IsUnicode(false) and unique, and why the delete behavior in the unit hierarchy is Restrict. Had we chosen Cascade, SQL Server would reject the constraint, because a foreign key pointing to its own table could create cycles or multiple cascade paths (we got the "may cause cycles or multiple cascade paths" error in a separate test database). More importantly, if it were allowed, deleting a campus would silently delete the departments under it too. Lines like these have to come from the business rule, not from completion.

There is also a job completion should never do: the migration. Once the configuration is done, we generate the migration with the tool, not by hand or by guessing:

$ dotnet ef migrations add AddInstitutions --project src/Bookstore.Api --output-dir Data/Migrations
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'

The generated Up method contains the institutional schema, two tables, two foreign keys and the seed rows. Reviewing migrations is the topic of the EF Core series; what matters here is that a migration file is not a file to write with completion. The same logic applies on the Angular side: in the templates of bookstore-web, money is always shown with the currency: 'TRY' : 'symbol-narrow' pipe and elements under test always have a data-testid. These patterns are easily completed from open files; but what a new signal should compute is still your decision.

The Cost of Tab in a Test

Back to the test from Guess First. OrderItem.CalculateLineTotal rounds the line total to two decimals and, following the rule discussed in the Requirements Analysis series, rounds half a cent away from zero (MidpointRounding.AwayFromZero). The whole test:

[Theory]
[InlineData("0.125", 1)]
[InlineData("0.145", 1)]
public void Line_total_matches_the_formula(string unitPrice, int quantity)
{
    decimal price = decimal.Parse(unitPrice, CultureInfo.InvariantCulture);

    // The expected value repeats the implementation's formula.
    decimal expected = Math.Round(price * quantity, 2);

    Assert.Equal(expected, OrderItem.CalculateLineTotal(price, quantity));
}
failed Bookstore.Api.Tests.OrderItemMirrorTests.Line_total_matches_the_formula(unitPrice: "0.125", quantity: 1) (6ms)
  Assert.Equal() Failure: Values differ
  Expected: 0.12
  Actual:   0.13
failed Bookstore.Api.Tests.OrderItemMirrorTests.Line_total_matches_the_formula(unitPrice: "0.145", quantity: 1) (0ms)
  Assert.Equal() Failure: Values differ
  Expected: 0.14
  Actual:   0.15
Test run summary: Failed!
  total: 5
  failed: 2
  succeeded: 3
  skipped: 0

The first answer: the code is right, the test is wrong. Math.Round(decimal, int) uses banker's rounding by default (it rounds a half to the nearest even number); the test encodes that default, not the business rule. In the ninth post of the IDEs series a boundary-value test caught banker's rounding; the difference here is that the bug is on the test side.

The real danger starts after this. Someone who sees the red test may forget that the test also came in with Tab, think "the code is wrong", remove AwayFromZero and turn the test green. We actually tried it:

failed Bookstore.Api.Tests.OrderItemTests.Line_total_is_rounded_to_two_decimals(unitPrice: "0.125", quantity: 1, expected: "0.13") (5ms)
  Assert.Equal() Failure: Values differ
  Expected: 0.13
  Actual:   0.12
failed Bookstore.Api.Tests.OrderItemTests.Line_total_is_rounded_to_two_decimals(unitPrice: "0.145", quantity: 1, expected: "0.15") (0ms)
  Assert.Equal() Failure: Values differ
  Expected: 0.15
  Actual:   0.14
Test run summary: Failed!
  total: 5
  failed: 2
  succeeded: 3
  skipped: 0

The new test turned green, but this time OrderItemTests, which was already in the repo, went red. That test's expected values are not written with a formula but with numbers picked by hand from the business rule: 0.13 for 0.125 and 0.15 for 0.145. That is exactly what saved us. Two lessons come out of this. Do not leave a test's expected value to completion; pick the number yourself from the business rule. And a test that computes the expected value with the same formula as the implementation either copies the implementation (and tests nothing) or, as in this example, encodes a different default. After the experiment we deleted the new test and reverted the code; only the test with hand-picked values stayed in the repo.

When to Turn It Off

There are a few good reasons to turn completion off: when suggestions break your train of thought while writing prose, when you are learning to solve an algorithm or an exam yourself, or when you are in a file with secrets. In VS Code you turn it on and off per language with the github.copilot.enable setting; * sets the default for all languages. The workspace setting we added to Bookstore:

{
  "github.copilot.enable": {
    "*": true,
    "plaintext": false,
    "markdown": false
  },
  "github.copilot.nextEditSuggestions.enabled": true,
  "editor.inlineSuggest.edits.showCollapsed": true
}

We turned Markdown off because the Markdown files in the repo (the README and the instruction files coming in later posts) are prose, and there a sentence written by a suggestion looks like a team decision. For a short break, Snooze in VS Code's Status Bar menu adds five minutes with each click. VS Code also does not start automatic ghost text and NES requests on a metered connection; manual triggering keeps working.

In Visual Studio the settings are under Tools > Options > Text Editor > Inline Suggestions. If "Inline Suggestions Invocation" is set to Manual, suggestions only come with Alt+. or Alt+,; "Show inline suggestions only after a pause in typing" adds a delay so that suggestions do not flash while you type. You can also change the accept key from Tab to the Right arrow.

Per-language switching has a limit: to turn it off for appsettings.Development.json you would have to turn off JSON, which affects every JSON file. A per-file rule is set with content exclusion in Copilot Business and Enterprise; the Visual Studio documentation says completion does not work in excluded files. Content exclusion and its limits are in the tenth post. The real safeguard does not change: a real secret should not sit in a plain file on a development machine.

In Production

Suggestions matching public code. In an organization, the "Suggestions matching public code" policy can block matching suggestions or allow them with a notice. When allowed, Visual Studio tells you when a completion matches code in a public GitHub repository; the Copilot log in the Output window shows a link to the license information and references to similar code. You make the license decision by looking at that entry.

Quality, not cost. On paid plans inline suggestions and NES spend no credits (first post); on the Free plan there is a limit of 2,000 completions a month. Base the decision to turn them off on the kind of file and the nature of the work, not on budget.

In review, do not ask about the source. A diff does not show whether a line was written with completion, and it does not matter; the responsibility lies with whoever made the commit. But asking where a test's expected value comes from catches mistakes like the one in this post during review.

Common Pitfalls

Tabbing a suggestion without reading it. Especially in long blocks, the last lines are guesses; use word and line accept to take only what you have read.

Letting completion write a test's expected value. As in this post, it either copies the implementation or encodes a wrong default; either way the test does not protect the business rule.

Changing the code automatically when you see a red test. First make sure the test is right; older tests written with hand-picked values are the referee here.

Writing migrations, lock files or generated code with completion. These are generated by tools.

Leaving completion on in files with secrets and thinking that turning it off per language solves this. The per-file solution is content exclusion; the real solution is not keeping the secret in a file.

A Small Experiment

★☆☆ For one week, work with only word and line accept instead of Tab. At the end of each day, note how many of the suggestions you accepted you later had to fix, and compare with the previous week.

★★☆ Add a string property named CostCenter to InstitutionUnit in Bookstore. First list the edits that follow from the change yourself (configuration, seed, endpoint record, test), then see which of them NES suggests. Generate the migration with dotnet ef.

★★★ Add three more boundary values to OrderItemTests (for example quantity 3 and unit price 0.335). Calculate the expected values by hand from the business rule; then run the same tests with MidpointRounding.ToEven and see which ones catch the difference.

Completion is the narrowest kind of help, working around the cursor. In the next post we widen the question: Copilot Chat's Ask, Plan and Agent flows, #-references, the workspace index, and asking "how does order cancellation work in the Ordering module?" with the right context.