Vim Tabs Guide for 2026: Tab Pages, Windows, Buffers, Sessions, and Indentation

Maintained layer, checked 2026-09-01. In Vim, a tab character and a tab page are unrelated features. This guide explains both, using current Vim and Neovim documentation. The complete 2019 export remains in a dated archive at the end and is not presented as current setup advice.

The most useful mental model is simple: a buffer holds text, a window displays a buffer, and a tab page holds a window layout. A session can restore a larger editing workspace. Once these objects are kept separate, opening, navigating, closing, mapping, and fixing indentation become predictable.

Two Meanings of “Tab”

Vim uses the word in two distinct ways:

  • A tab character is horizontal-tab byte 9 inside text. Options such as tabstop, softtabstop, shiftwidth, and expandtab control how indentation is displayed or inserted.
  • A tab page is a collection of one or more windows. It is a workspace layout, not a file container and not a buffer.

Pressing the keyboard Tab key in Insert mode does not create a tab page. Likewise, :tabnew does not change indentation. Keeping these two meanings separate resolves much of the confusion in the original note.

Buffers, Windows, Tab Pages, and Sessions

Object What it represents Inspect it Typical action
Buffer Text loaded into memory, usually from a file :ls :buffer {name-or-number}
Window A viewport onto one buffer :tabs or CTRL-W w :split, :vsplit, :close
Tab page One collection of windows :tabs :tabedit, gt, :tabclose
Session A Vim script that restores windows, tab pages, and selected state :set sessionoptions? :mksession, vim -S

The same buffer may appear in several windows, including windows on different tab pages. Closing one window therefore does not necessarily delete its buffer, and closing a tab page does not mean “close one file.”

A Reproducible Two-Workspace Exercise

Start Vim or Neovim with one file:

vim README.md

Then build two layouts using only built-in commands:

:vsplit src/main.py
:tabedit tests/test_main.py
:split tests/test_cli.py
:tabs

The result is:

Tab page 1
├── window: README.md buffer
└── window: src/main.py buffer

Tab page 2
├── window: tests/test_main.py buffer
└── window: tests/test_cli.py buffer

Use gt and gT to move between tab pages. Within one tab page, use CTRL-W h, CTRL-W j, CTRL-W k, and CTRL-W l to move among windows. Run :ls to see buffers independently of the visible layout.

The filenames are examples; substitute files that exist in a disposable repository. If a named file does not exist, Vim creates a new, unsaved buffer for that name.

Open, Navigate, Inspect, and Move Tab Pages

These built-in commands cover most tab-page work:

:tabedit path/to/file
:tabnew
:tabs
:tabnext
:tabprevious
:tabfirst
:tablast
:tabmove 0
:tabmove
  • :tabedit {file} opens a new tab page and edits the file there.
  • :tabnew opens a new tab page with an empty window.
  • :tabs lists every tab page and its windows; a plus sign identifies a modified buffer.
  • gt and :tabnext go to the next tab page; gT and :tabprevious go to the previous one.
  • {count}gt, such as 2gt, selects a tab page by its current number.
  • :tabmove 0 moves the current tab page to the beginning; :tabmove moves it to the end.

Tab-page numbers can change after a page is opened, closed, or moved. Treat them as current positions rather than permanent identifiers.

Work With Windows and Buffers Directly

Use a window when you need another view and a buffer command when you need different text:

:split path/to/file
:vsplit path/to/file
:close
:only

:ls
:buffer 3
:bnext
:bprevious
:bdelete 3

:close removes the current window. :only closes the other windows in the current tab page when it can do so safely. Neither command means “delete the file.”

:buffer, :bnext, and :bprevious change which buffer the current window displays. :bdelete removes a buffer from the buffer list and closes windows that display it; without !, it refuses to abandon unsaved changes. Use :close instead when the goal is only to remove one view.

Close the Right Thing Safely

The command without ! is normally the safer starting point:

Intent Command What happens
Save the current buffer :write Writes this buffer to its file
Save all changed buffers :wall Attempts to write every changed buffer
Close one window :close Keeps other views and buffers where possible
Close the current tab page :tabclose Closes all its windows; refuses unsafe abandonment
Remove one buffer :bdelete {name} Removes it from the list; refuses unsaved loss
Quit the current window :quit Exits Vim only if it was the last window

Avoid adding ! reflexively: with commands such as :quit! or :bdelete!, it can discard changes. When Vim reports an unsaved buffer, inspect :ls, use :write if the change is wanted, or make an explicit discard decision.

Before simplifying a large layout, a conservative sequence is:

:ls
:wall
:tabs
:tabclose

:wall can still fail for unnamed, read-only, or otherwise unwritable buffers, so read the messages rather than assuming everything was saved.

Save and Restore a Session

A session is an executable Vim script that can restore the window layout, tab pages, buffers, and selected options according to sessionoptions.

Inspect the policy and save the current workspace:

:set sessionoptions?
:wall
:mksession Session.vim

The non-bang form refuses to overwrite an existing session file. When intentionally updating that file, use:

:wall
:mksession! Session.vim

Restore it from the shell:

vim -S Session.vim

For Neovim, use nvim -S Session.vim instead.

Or restore it inside Vim:

:source Session.vim

Sessions include tab pages unless tabpages has been removed from sessionoptions. They do not restore every kind of state, and plugin behavior may add its own effects. Because a session is sourced as code, review it and load only session files you trust. Do not commit a session containing private paths unless those paths are intended to be public.

Add Minimal, Inspectable Mappings

Learn gt, gT, and the Ex commands before adding mappings. If a configuration needs mnemonic keys, use non-recursive normal-mode mappings and keep the safe, non-bang commands visible:

let mapleader = " "
nnoremap <silent> <leader>tn <Cmd>tabnew<CR>
nnoremap <silent> <leader>tc <Cmd>tabclose<CR>
nnoremap <silent> <leader>tl <Cmd>tabs<CR>

Inspect an unexpected mapping and the script that last defined it:

:verbose nmap gt
:verbose nmap <leader>tc

For a Neovim Lua configuration, the equivalent explicit mappings are:

vim.g.mapleader = " "
vim.keymap.set("n", "<leader>tn", "<cmd>tabnew<cr>", { desc = "New tab page" })
vim.keymap.set("n", "<leader>tc", "<cmd>tabclose<cr>", { desc = "Close tab page" })
vim.keymap.set("n", "<leader>tl", "<cmd>tabs<cr>", { desc = "List tab pages" })

Do not install a large configuration merely to obtain tab navigation. Start with built-ins, add mappings you can explain, and check for collisions with :verbose nmap.

Configure Tab Characters and Indentation

These are buffer-local options, so inspect them in the affected file:

:setlocal tabstop? shiftwidth? softtabstop? expandtab?
  • tabstop controls the displayed columns occupied by a literal tab character.
  • shiftwidth controls indentation steps used by commands such as >>, <<, and automatic indentation.
  • softtabstop controls how Tab and Backspace behave while editing.
  • expandtab inserts spaces instead of a literal tab in common insertion and indentation operations.

A conventional four-space, spaces-only policy is:

setlocal expandtab
setlocal tabstop=4
setlocal shiftwidth=4
setlocal softtabstop=4

A tab-based project might instead require:

setlocal noexpandtab
setlocal tabstop=8
setlocal shiftwidth=8
setlocal softtabstop=8

These are examples, not universal rules. Follow the repository’s existing style. Changing tabstop changes how literal tabs look; it does not rewrite the file.

Inspect and Normalize an Existing File Carefully

Make invisible whitespace visible before changing anything:

:setlocal list
:setlocal listchars=tab:>-,trail:·

After choosing the project policy, a spaces-only conversion can be performed in memory:

:setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4
:%retab

Without !, :retab targets whitespace sequences that contain a tab; with expandtab, it replaces tabs with the appropriate number of spaces while preserving displayed alignment. It can still alter tabs inside strings or data, so inspect the buffer and version-control diff before accepting the change. Use u to undo an unwanted in-memory conversion.

Then write deliberately and inspect the repository diff:

:write

The archived command that globally replaces eight literal spaces with four does not replace tab characters and can damage intentional alignment. It is preserved for provenance, not recommended as a general repair.

Apply Filetype-Specific Rules

Place durable rules in a configuration you control. This Vimscript example uses an augroup so re-sourcing the file does not duplicate autocommands:

augroup indentation_rules
  autocmd!
  autocmd FileType python setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4
  autocmd FileType c setlocal noexpandtab tabstop=8 shiftwidth=8 softtabstop=8
augroup END

The C line is only an example; many C projects use spaces or a different width. Match the project instead of the language name alone.

When an option keeps changing, ask Vim where its current value was last set:

:verbose setlocal expandtab?
:verbose setlocal tabstop?
:verbose setlocal shiftwidth?
:verbose setlocal softtabstop?

This often identifies a filetype plugin, autocommand, modeline, or configuration layer that overrode the expected value.

Diagnose Vim and Other-Editor Inconsistency

When a file aligns in Vim but not in Notepad++ or another editor, diagnose before converting:

  1. Run :setlocal list to distinguish literal tabs from spaces.
  2. Query tabstop, shiftwidth, softtabstop, and expandtab.
  3. Check the other editor’s tab display width and whether its Tab key inserts tabs or spaces.
  4. Confirm the repository’s intended convention from existing files and tooling.
  5. Convert only if the file is actually inconsistent; then inspect the diff.

Display width and file content are different problems. Two editors can display the same literal tab differently, while two files that look identical can contain different bytes.

Troubleshooting

  • gt does nothing: there may be only one tab page, or a mapping may override it. Run :tabs and :verbose nmap gt.
  • Closing a tab did not remove a file from :ls: the buffer can remain loaded or hidden after its windows close. Use :bdelete {name} only if removing the buffer is the intent.
  • :bdelete affected several views: several windows were displaying the same buffer. Use :close to remove just one window.
  • Vim reports “No write since last change”: this is a safety check. Save the buffer or explicitly decide whether the change should be discarded.
  • A restored session changes mappings or options: inspect sessionoptions and the generated session before sourcing it; sessions can restore selected global state.
  • Tab labels appear to name files: a label represents a tab page’s window collection, not ownership of one buffer.
  • :%retab produced a large diff: undo it, confirm the original tabstop, and check whether tabs occur in data or strings before retrying.

Primary Vim and Neovim Documentation

Original 2019 Archive (Verbatim)

The following is the complete WordPress export, including its original metadata, wording, order, commands, and headings. It was published and last modified on 2019-04-21. It is historical material, not the maintained recommendation above. Only invisible trailing whitespace has been normalized for repository formatting.

---
id: 1876
title: 'Vim Configuration and Tab Inconsistency Between Vim and Notepad++'
slug: 'vim-configuration-and-tab-inconsistency-between-vim-and-notepad'
date: '2019-04-21T03:42:35'
modified: '2019-04-21T03:42:42'
status: 'publish'
link: 'https://blog.lazying.art/en/html/computer_internet/unix_linux/v_ivim/1876/vim-configuration-and-tab-inconsistency-between-vim-and-notepad.html'
author: 'Lachlan Chen'
categories:
  - 'VI&amp;VIM'
---

General

A completed configuration can be installed by running this

git clone –depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime
sh ~/.vim_runtime/install_awesome_vimrc.sh


If you want to add some customized configurations,

vim ~/.vim_runtime/my_configs.vim


Some sample configuration of your vim

set shiftwidth=4 ” used by >>, << and tab.
set tabstop=4 ” number of space characters used when displaying TAB
set expandtab ” replace TAB by spaces
set softtabstop=4 ” will erase 4 spaces at once when using BS in the begining of lines only


If this is an existed file, you might run this to sort out your file

:%retab


Python

C

Vim and Notepad++ Inconsistency

To solve the inconsistency, you might want to use

:%s/ / /g

Leave a Reply