pw_ide: Add settings

Defines common configuration available to all pw_ide functionality.
Projects and users can choose to create a .pw_ide.yaml file that
overrides the default configuration.

The working directory is where pw_ide stores all state and data needed
to provide IDE features. This is specific to the user/workstation and
shouldn't be committed to the repo.

Change-Id: I6d02bf35afc41ca164cdfb15ec89faa984046b49
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/110252
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
Commit-Queue: Chad Norvell <chadnorvell@google.com>
diff --git a/.gitignore b/.gitignore
index 7f84ff5..2837f1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
 docs/_build
 
 # Editors
+.pw_ide/
 .idea/
 .ijwb/
 .project
diff --git a/pw_ide/docs.rst b/pw_ide/docs.rst
index 05771e1..eb16cb8 100644
--- a/pw_ide/docs.rst
+++ b/pw_ide/docs.rst
@@ -5,3 +5,13 @@
 ------
 This module provides tools for supporting code editor and IDE features for
 Pigweed projects.
+
+Configuration
+=============
+Pigweed IDE settings are stored in the project root in ``.pw_ide.yaml``, in which
+these options can be configured:
+
+* ``working_dir``: The working directory for compilation databases and caches
+  (by default this is `.pw_ide` in the project root). This directory shouldn't
+  be committed to your repository or be a directory that is routinely deleted or
+  manipulated by other processes.
diff --git a/pw_ide/py/BUILD.gn b/pw_ide/py/BUILD.gn
index 0a68e09..d9af1dc 100644
--- a/pw_ide/py/BUILD.gn
+++ b/pw_ide/py/BUILD.gn
@@ -25,6 +25,7 @@
   sources = [
     "pw_ide/__init__.py",
     "pw_ide/__main__.py",
+    "pw_ide/settings.py",
   ]
   tests = []
   python_deps = [ "$dir_pw_console/py" ]
diff --git a/pw_ide/py/pw_ide/settings.py b/pw_ide/py/pw_ide/settings.py
new file mode 100644
index 0000000..a2a07df
--- /dev/null
+++ b/pw_ide/py/pw_ide/settings.py
@@ -0,0 +1,63 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""pw_ide configuration."""
+
+import os
+from pathlib import Path
+from typing import Any, Dict, Optional, Union
+
+from pw_console.yaml_config_loader_mixin import YamlConfigLoaderMixin
+
+PW_IDE_DIR_NAME = '.pw_ide'
+
+_PW_IDE_DEFAULT_DIR = Path(
+    os.path.expandvars('$PW_PROJECT_ROOT')) / PW_IDE_DIR_NAME
+
+_DEFAULT_CONFIG = {
+    'working_dir': _PW_IDE_DEFAULT_DIR,
+}
+
+_DEFAULT_PROJECT_FILE = Path('$PW_PROJECT_ROOT/.pw_ide.yaml')
+_DEFAULT_PROJECT_USER_FILE = Path('$PW_PROJECT_ROOT/.pw_ide.user.yaml')
+_DEFAULT_USER_FILE = Path('$HOME/.pw_ide.yaml')
+
+
+class IdeSettings(YamlConfigLoaderMixin):
+    """Pigweed IDE features settings storage class."""
+    def __init__(
+        self,
+        project_file: Union[Path, bool] = _DEFAULT_PROJECT_FILE,
+        project_user_file: Union[Path, bool] = _DEFAULT_PROJECT_USER_FILE,
+        user_file: Union[Path, bool] = _DEFAULT_USER_FILE,
+        default_config: Optional[Dict[str, Any]] = None,
+    ) -> None:
+        self.config_init(
+            config_section_title='pw_ide',
+            project_file=project_file,
+            project_user_file=project_user_file,
+            user_file=user_file,
+            default_config=_DEFAULT_CONFIG
+            if default_config is None else default_config,
+            environment_var='PW_IDE_CONFIG_FILE',
+        )
+
+    @property
+    def working_dir(self) -> Path:
+        """Path to the pw_ide working directory.
+
+        This should not be a directory that's regularly deleted or manipulated
+        by other processes (e.g. the GN `out` directory) nor should it be
+        committed to the code repo.
+        """
+        return Path(self._config.get('working_dir', ''))