Skip to main content

Cursor Movement with CSI Sequences

CodeAbbrName
CSI # ACUUCUrsor Up
CSI # BCUDCUrsor Down
CSI # CCUFCUrsor Forward
CSI # DCUBCUrsor Backward
CSI # ECNLCUrsor Next Line
CSI # FCPLCUrsor Previous Line
CSI # ICHTCursor Horizontal forward Tabulation
CSI # ZCBTCursor Backward Tabulation
CSI # GCHACursor Horizontal Absolute
CSI # ; # HCUPCUrsor Position

Today, we will continue from the previous article to explore how to move the cursor using CSI sequences. The types of CSI sequences for moving the cursor can be summarized as follows.

CUU, CUD, CUF, CUB

These are the abbreviations for CUrsor Up, CUrsor Down, CUrsor Forward, and CUrsor Backward; as the names suggest, they move the cursor up, down, forward, and backward. They take a single number as an argument; if the argument is omitted, it is treated as 1. Thus, 0x1b[A is equivalent to 0x1b[1A.

In this case, CUF and CUB move only within the same line. In other words, CUB received at the beginning of a line does not move the cursor to the previous line, and CUF received at the end of a line does not move the cursor to the next line. To achieve this behavior, you need to turn on reverse auto-wrap mode and auto-wrap mode, respectively, but support for these modes depends on the terminal implementation.

CNL, CPL

These stand for Cursor Next Line and Cursor Previous Line, respectively, and move the cursor to the next or previous line. While CUU and CUD keep the cursor's column, CNL and CPL move the cursor to the beginning of the line. In other words, CNL is a combination of CR(\r, carriage return) and CUD; CPL is a combination of CR and CUU. They also take a single number as an argument; if the argument is not provided, the default value is treated as 1.

CHT, CBT

CHT stands for Cursor Horizontal Forward Tabulation, and CBT stands for Cursor Backward Tabulation. They move the cursor to the next, and the previous tab stops, respectively.

CHA, CUP

The above sequences move the cursor from its current position to a relative position. However, CHA and CUP move the cursor to the absolute coordinates received. The difference is that CHA takes a single number as an argument and moves the cursor to the specified column in the current line, while CUP takes two numbers, a line, and a column, and moves the cursor to the specified coordinates. If no numbers are provided, the default value is treated as 1.

People familiar with programming may wonder why the default value is not 0 but 1. This is because terminals use a coordinate system that starts at (1, 1) instead of (0, 0). The top-left coordinate in the terminal is (1, 1).

So far, we have looked at how to move the cursor using CSI sequences. Freely controlling the cursor is the minimum building block for creating a TUI. Using this, you can implement various components on the terminal, not only a simple output stream that flows downward.

Comments

Popular posts from this blog

Type Conversion in Rust

Type conversion is not special in Rust. It's just a function that takes ownership of the value and returns the other type. So you can name convert functions anything. However, it's a convention to use as_ , to_ , and into_ prefixed name or to use from_ prefixed constructor. From You can create any function for type conversion. However, if you want to provide generic interfaces, you'd better implement the From trait. For instance, you should implement From<X> for Y when you want the interface that converts the X type value to the Y type value. The From trait have an associated function named from . You can call this function like From::from(x) . You also can call it like Y::from(x) if the compiler cannot infer the type of the destination type. Into From have an associated function, it makes you be able to specify the destination type. It's why From has an associated function instead of a method, but on the other hands, you cannot use it as a me

Do not use garbage collection to catch memory leak

Garbage collection is a technique that automatically releases unnecessary memory. It's very famous because many programming languages adopted garbage collection after John McCarthy implemented it in Lisp. However, there are a few people who misunderstand what garbage collection does. If you think garbage collection prevents a memory leak, unfortunately, you are one of them. Garbage collection cannot prevent a memory leak. There is no way to avoid all memory leaks if you are using Turing-complete language. To understand it you should know what a memory leak is. Wikipedia describes a memory leak as the following: a type of resource leak that occurs when a computer program incorrectly manages memory allocations in such a way that memory which is no longer needed is not released. Briefly, a memory leak is a bug that doesn't release a memory that you don't use. So it is first to find the memory which will not be used in order to detect memory leaks. Unfortunately, it i

[C++] Handling Exceptions in Constructors

When you use RAII idiom, there are often situations where constructors have to do complex tasks. These complex tasks can sometimes fail, resulting in throwing exceptions. This raises a concern: Is it okay to throw exceptions in constructors? The first concern is memory leaks. Fortunately, memory leaks do not occur. Variables created on the stack are released through stack unwinding, and if an exception occurs during heap allocation with the new operator, the new operator automatically deallocates the memory and returns nullptr . The next concern is whether the destructor of the member variables will be called correctly. However, this is also not a problem. When an exception occurs, member variables can be divided into three categories: fully initialized member variables, member variables being initialized, and uninitialized member variables. Fully initialized member variables have had their constructors called and memory allocations completed successfully. In the example code, t