# Style Guide

We mostly use the [GTK style
guide](https://gitlab.gnome.org/GNOME/gtk/-/blob/main/docs/CODING-STYLE.md)
by default. Some exceptions are listed below.

We also target the same [toolchain
requirements](https://github.com/GNOME/glib/blob/main/docs/toolchain-requirements.md)
as glib.

## General Style hints
* Overally, we prefer code legibility over brevity or cleverness.
* Declare variables at the top of the scope, in order they're
  used. Mid-function declarations are discouraged.
* Always use `g_autofree` and `g_autoptr` where possible. Do one per
  line, and make sure they're initialized to NULL. static-scan will complain
  if they aren't.
  ```C
  g_autofree gchar *str = NULL; // This is fine
  g_autofree gchar *str2; // Not initialized to NULL. This is wrong
  ```
    * Similarly, use `g_steal_pointer()` and friends. They make the
      lifecycle of variables more understandable.
* Include the minimum number of header files, and list them in
  alphabetical order.
* Use spaces instead of tabs. We use 2 spaces for indentation. Run
  this command to check for any occurances of tabs:
  ```bash
  $ grep -r $'\t'
  ```
* Use C-style comments for explanitary notes. `/* . . . */` instead of
  C++-style `//`. The latter are reserved for transient issues.
   * Example: If you have a debugging print-statement that's often
     used, feel free to use `//`. Ex:
     ```C
       //puzzle_set_model_print (self);
     ```
   * Example 2: For things that are broken and need to be fixed, we
     sometimes temporarily will use `//` as well.
* We follow the GTK brace convention with if statements, though are a
  bit more liberal in using braces:
  ```C
  if (condition)
    do_one_thing ();

  if (condition)
    do_one_thing ();
  else
    do_another_thing ();

  if (condition)
    {
      do_one_thing ();
      do_another_thing ();
    }
  else
    {
      // Different than GTK convention
      do_a_third_thing ();
    }
  ```
  That last example is a little different than the GTK style-guide,
  which might omit the last braces.

## Naming Conventions
* **Functions and variables**: Use `snake_case`.
* **Types**: Use `PascalCase` (e.g., `PlayWindow`, `GridState`).
* **CSS classes**: Use `kebab-case`.
* **Callbacks**: Always use a `_cb` suffix (e.g., `button_clicked_cb`).
* **Internal functions**: Functions that are shared across files but
  not part of the public API should be prefixed with an underscore
  (e.g., `_play_window_update()`). These are often declared in a
  `-private.h` header.

## Error Handling
* Use `g_return_if_fail()` and `g_return_val_if_fail()` at the
  beginning of public functions to validate arguments.
* Use `g_assert()` for internal consistency checks that should never
  fail if the code is correct.
* Use `g_warning()` or `g_critical()` for unexpected runtime errors
  that aren't fatal.

## UI Development
* We use
  [Blueprint](https://jwestman.pages.gitlab.gnome.org/blueprint-compiler/)
  for UI definitions. These are files ending in `.blp`.
* Complex widgets should use UI templates. Bind children and callbacks
  in the `class_init` function using
  `gtk_widget_class_bind_template_child()` and
  `gtk_widget_class_bind_template_callback()`.

## Private Headers
If a widget or class has internal state or methods that need to be
shared across multiple source files (e.g., `play-window.c` and
`play-window-actions.c`), use a private header file named
`filename-private.h`. This helps keep the public header clean and
encapsulates internal implementation details.

## Stateless Model
Much of the editor logic and the `PlayGrid` use a stateless
model. Actions on the grid state should not modify the existing state
object but instead return a new state object (often a clone with
modifications).
* Functions that return a new state should be marked with
  `G_GNUC_WARN_UNUSED_RESULT`.
* The `GridState` struct is a plain C struct (not a GObject) and
  should be managed with `grid_state_new()`, `grid_state_clone()`, and
  `grid_state_free()`.

## FIXME and TODO messages

If you're going to leave a FIXME in the code, please put the _domain_
in parens. That make it easy to grep for all common areas that may
need fixing. As an example:

```C
/* FIXME(css): This color is hardcoded instead of css based. See bug #xxx */
```

We don't use TODO or have a well maintained TODO.md file. Please file
bugs for issues.

Common FIXME domains:
* *error*: an unhandled error, that probably needs a better message to
  the user than a g_warning.
* *refactor*: clean up an egregiously messy area of code
* *optimization*: a chunk of code is unnecessarily slow/big
* *css*: Something that should be moved to be a CSS setting
* *gtk/adwaita/libfoo*: Work around for an issue in a
  dependency. Often includes the version number it was fixed or
  introduced.
* *magichars*: A hardcoded constant in the code, that should be
  centralized or made configurable
* *mystery*: Something we don't understand in the code and couldn't
  figure out.

## File order

Within a file, we use the following section order. Keep two newlines
between each section, and one newline within each section:

* License
* Includes
* Local #defines and enums. Global tables
* Object properties and signals
* Object structs
* Local headers
* G_DEFINE_TYPE()
* Object methods, with small helper functions immediately above the caller
* Local methods, in order they're called
* Public methods
* Global helper functions

Note, this is mostly aspirational, and has settled over time. Very few
files follow this to the letter. We fix them as we see them.

## Trailing whitespace

We kill all whitespace after any line. To check if the changes in your
current branch have any trailing whitespace, run this command:
```bash
$ git diff --check master
```
If your branch isn't based on `master`, then replace `master` with
the actual base branch or commit.

To check for all trailing whitespace in the codebase (not just
those from your current branch), run this command:
```bash
# Do a diff check against the first ever commit,
# and exclude any dirs that we expect to contain trailing whitespace.

# NOTE: You must run this command in the root crosswords directory,
# in order for the exclusions to work.

$ git diff --check 460554e5 -- ':(exclude)word-lists/' ':(exclude)puzzle-sets/'
```

Please include a trailing newline at the end of the file.
