diff --git a/package-lock.json b/package-lock.json
index 71584db51..c7d8b1ba0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,1195 @@
 {
+    "name": "csharp-language-server-protocol",
+    "lockfileVersion": 2,
     "requires": true,
-    "lockfileVersion": 1,
+    "packages": {
+        "": {
+            "devDependencies": {
+                "husky": "^4.3.0",
+                "lint-staged": "^10.5.1",
+                "prettier": "^2.1.2"
+            }
+        },
+        "node_modules/@babel/code-frame": {
+            "version": "7.10.4",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+            "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+            "dev": true,
+            "dependencies": {
+                "@babel/highlight": "^7.10.4"
+            }
+        },
+        "node_modules/@babel/helper-validator-identifier": {
+            "version": "7.10.4",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+            "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+            "dev": true
+        },
+        "node_modules/@babel/highlight": {
+            "version": "7.10.4",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+            "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+            "dev": true,
+            "dependencies": {
+                "@babel/helper-validator-identifier": "^7.10.4",
+                "chalk": "^2.0.0",
+                "js-tokens": "^4.0.0"
+            }
+        },
+        "node_modules/@babel/highlight/node_modules/ansi-styles": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+            "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+            "dev": true,
+            "dependencies": {
+                "color-convert": "^1.9.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/@babel/highlight/node_modules/chalk": {
+            "version": "2.4.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+            "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^3.2.1",
+                "escape-string-regexp": "^1.0.5",
+                "supports-color": "^5.3.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/@babel/highlight/node_modules/color-convert": {
+            "version": "1.9.3",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+            "dev": true,
+            "dependencies": {
+                "color-name": "1.1.3"
+            }
+        },
+        "node_modules/@babel/highlight/node_modules/color-name": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+            "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+            "dev": true
+        },
+        "node_modules/@babel/highlight/node_modules/has-flag": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/@babel/highlight/node_modules/supports-color": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/@types/color-name": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+            "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+            "dev": true
+        },
+        "node_modules/@types/parse-json": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+            "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
+            "dev": true
+        },
+        "node_modules/aggregate-error": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+            "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+            "dev": true,
+            "dependencies": {
+                "clean-stack": "^2.0.0",
+                "indent-string": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ansi-colors": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+            "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/ansi-escapes": {
+            "version": "4.3.1",
+            "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
+            "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
+            "dev": true,
+            "dependencies": {
+                "type-fest": "^0.11.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ansi-regex": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+            "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ansi-styles": {
+            "version": "4.2.1",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+            "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+            "dev": true,
+            "dependencies": {
+                "@types/color-name": "^1.1.1",
+                "color-convert": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/astral-regex": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+            "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/braces": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+            "dev": true,
+            "dependencies": {
+                "fill-range": "^7.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/callsites": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+            "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/chalk": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+            "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/ci-info": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+            "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+            "dev": true
+        },
+        "node_modules/clean-stack": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+            "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/cli-cursor": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+            "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+            "dev": true,
+            "dependencies": {
+                "restore-cursor": "^3.1.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/cli-truncate": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+            "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+            "dev": true,
+            "dependencies": {
+                "slice-ansi": "^3.0.0",
+                "string-width": "^4.2.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/color-convert": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+            "dev": true,
+            "dependencies": {
+                "color-name": "~1.1.4"
+            },
+            "engines": {
+                "node": ">=7.0.0"
+            }
+        },
+        "node_modules/color-name": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+            "dev": true
+        },
+        "node_modules/commander": {
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
+            "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
+            "dev": true,
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/compare-versions": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
+            "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
+            "dev": true
+        },
+        "node_modules/cosmiconfig": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
+            "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==",
+            "dev": true,
+            "dependencies": {
+                "@types/parse-json": "^4.0.0",
+                "import-fresh": "^3.2.1",
+                "parse-json": "^5.0.0",
+                "path-type": "^4.0.0",
+                "yaml": "^1.10.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/cross-spawn": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+            "dev": true,
+            "dependencies": {
+                "path-key": "^3.1.0",
+                "shebang-command": "^2.0.0",
+                "which": "^2.0.1"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/debug": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
+            "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
+            "dev": true,
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            }
+        },
+        "node_modules/dedent": {
+            "version": "0.7.0",
+            "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+            "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
+            "dev": true
+        },
+        "node_modules/emoji-regex": {
+            "version": "8.0.0",
+            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+            "dev": true
+        },
+        "node_modules/end-of-stream": {
+            "version": "1.4.4",
+            "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+            "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+            "dev": true,
+            "dependencies": {
+                "once": "^1.4.0"
+            }
+        },
+        "node_modules/enquirer": {
+            "version": "2.3.6",
+            "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+            "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+            "dev": true,
+            "dependencies": {
+                "ansi-colors": "^4.1.1"
+            },
+            "engines": {
+                "node": ">=8.6"
+            }
+        },
+        "node_modules/error-ex": {
+            "version": "1.3.2",
+            "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+            "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+            "dev": true,
+            "dependencies": {
+                "is-arrayish": "^0.2.1"
+            }
+        },
+        "node_modules/escape-string-regexp": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
+        "node_modules/execa": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+            "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+            "dev": true,
+            "dependencies": {
+                "cross-spawn": "^7.0.0",
+                "get-stream": "^5.0.0",
+                "human-signals": "^1.1.1",
+                "is-stream": "^2.0.0",
+                "merge-stream": "^2.0.0",
+                "npm-run-path": "^4.0.0",
+                "onetime": "^5.1.0",
+                "signal-exit": "^3.0.2",
+                "strip-final-newline": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/figures": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+            "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+            "dev": true,
+            "dependencies": {
+                "escape-string-regexp": "^1.0.5"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/fill-range": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+            "dev": true,
+            "dependencies": {
+                "to-regex-range": "^5.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/find-up": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+            "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+            "dev": true,
+            "dependencies": {
+                "locate-path": "^5.0.0",
+                "path-exists": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/find-versions": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz",
+            "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==",
+            "dev": true,
+            "dependencies": {
+                "semver-regex": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/get-own-enumerable-property-symbols": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
+            "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
+            "dev": true
+        },
+        "node_modules/get-stream": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+            "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+            "dev": true,
+            "dependencies": {
+                "pump": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/human-signals": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
+            "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.12.0"
+            }
+        },
+        "node_modules/husky": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz",
+            "integrity": "sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA==",
+            "dev": true,
+            "hasInstallScript": true,
+            "dependencies": {
+                "chalk": "^4.0.0",
+                "ci-info": "^2.0.0",
+                "compare-versions": "^3.6.0",
+                "cosmiconfig": "^7.0.0",
+                "find-versions": "^3.2.0",
+                "opencollective-postinstall": "^2.0.2",
+                "pkg-dir": "^4.2.0",
+                "please-upgrade-node": "^3.2.0",
+                "slash": "^3.0.0",
+                "which-pm-runs": "^1.0.0"
+            },
+            "bin": {
+                "husky-run": "bin/run.js",
+                "husky-upgrade": "lib/upgrader/bin.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/import-fresh": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+            "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+            "dev": true,
+            "dependencies": {
+                "parent-module": "^1.0.0",
+                "resolve-from": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/indent-string": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+            "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/is-arrayish": {
+            "version": "0.2.1",
+            "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+            "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+            "dev": true
+        },
+        "node_modules/is-fullwidth-code-point": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+            "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/is-number": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.12.0"
+            }
+        },
+        "node_modules/is-obj": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+            "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/is-regexp": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+            "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/is-stream": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
+            "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/isexe": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+            "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+            "dev": true
+        },
+        "node_modules/js-tokens": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+            "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+            "dev": true
+        },
+        "node_modules/json-parse-better-errors": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+            "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+            "dev": true
+        },
+        "node_modules/lines-and-columns": {
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
+            "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
+            "dev": true
+        },
+        "node_modules/lint-staged": {
+            "version": "10.5.1",
+            "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.1.tgz",
+            "integrity": "sha512-fTkTGFtwFIJJzn/PbUO3RXyEBHIhbfYBE7+rJyLcOXabViaO/h6OslgeK6zpeUtzkDrzkgyAYDTLAwx6JzDTHw==",
+            "dev": true,
+            "dependencies": {
+                "chalk": "^4.1.0",
+                "cli-truncate": "^2.1.0",
+                "commander": "^6.2.0",
+                "cosmiconfig": "^7.0.0",
+                "debug": "^4.2.0",
+                "dedent": "^0.7.0",
+                "enquirer": "^2.3.6",
+                "execa": "^4.1.0",
+                "listr2": "^3.2.2",
+                "log-symbols": "^4.0.0",
+                "micromatch": "^4.0.2",
+                "normalize-path": "^3.0.0",
+                "please-upgrade-node": "^3.2.0",
+                "string-argv": "0.3.1",
+                "stringify-object": "^3.3.0"
+            },
+            "bin": {
+                "lint-staged": "bin/lint-staged.js"
+            }
+        },
+        "node_modules/listr2": {
+            "version": "3.2.2",
+            "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.2.tgz",
+            "integrity": "sha512-AajqcZEUikF2ioph6PfH3dIuxJclhr3i3kHgTOP0xeXdWQohrvJAAmqVcV43/GI987HFY/vzT73jYXoa4esDHg==",
+            "dev": true,
+            "dependencies": {
+                "chalk": "^4.1.0",
+                "cli-truncate": "^2.1.0",
+                "figures": "^3.2.0",
+                "indent-string": "^4.0.0",
+                "log-update": "^4.0.0",
+                "p-map": "^4.0.0",
+                "rxjs": "^6.6.3",
+                "through": "^2.3.8"
+            },
+            "engines": {
+                "node": ">=10.0.0"
+            }
+        },
+        "node_modules/locate-path": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+            "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+            "dev": true,
+            "dependencies": {
+                "p-locate": "^4.1.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/log-symbols": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
+            "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+            "dev": true,
+            "dependencies": {
+                "chalk": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/log-update": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
+            "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
+            "dev": true,
+            "dependencies": {
+                "ansi-escapes": "^4.3.0",
+                "cli-cursor": "^3.1.0",
+                "slice-ansi": "^4.0.0",
+                "wrap-ansi": "^6.2.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/log-update/node_modules/slice-ansi": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+            "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "astral-regex": "^2.0.0",
+                "is-fullwidth-code-point": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/merge-stream": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+            "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+            "dev": true
+        },
+        "node_modules/micromatch": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+            "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+            "dev": true,
+            "dependencies": {
+                "braces": "^3.0.1",
+                "picomatch": "^2.0.5"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/mimic-fn": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+            "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+            "dev": true
+        },
+        "node_modules/normalize-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+            "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/npm-run-path": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+            "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+            "dev": true,
+            "dependencies": {
+                "path-key": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/once": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+            "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+            "dev": true,
+            "dependencies": {
+                "wrappy": "1"
+            }
+        },
+        "node_modules/onetime": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+            "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+            "dev": true,
+            "dependencies": {
+                "mimic-fn": "^2.1.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/opencollective-postinstall": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
+            "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
+            "dev": true,
+            "bin": {
+                "opencollective-postinstall": "index.js"
+            }
+        },
+        "node_modules/p-limit": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+            "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+            "dev": true,
+            "dependencies": {
+                "p-try": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/p-locate": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+            "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+            "dev": true,
+            "dependencies": {
+                "p-limit": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/p-map": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+            "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+            "dev": true,
+            "dependencies": {
+                "aggregate-error": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/p-try": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+            "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/parent-module": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+            "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+            "dev": true,
+            "dependencies": {
+                "callsites": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/parse-json": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz",
+            "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==",
+            "dev": true,
+            "dependencies": {
+                "@babel/code-frame": "^7.0.0",
+                "error-ex": "^1.3.1",
+                "json-parse-better-errors": "^1.0.1",
+                "lines-and-columns": "^1.1.6"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/path-exists": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+            "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/path-key": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+            "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/path-type": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+            "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/picomatch": {
+            "version": "2.2.2",
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+            "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.6"
+            }
+        },
+        "node_modules/pkg-dir": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+            "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+            "dev": true,
+            "dependencies": {
+                "find-up": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/please-upgrade-node": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+            "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
+            "dev": true,
+            "dependencies": {
+                "semver-compare": "^1.0.0"
+            }
+        },
+        "node_modules/prettier": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz",
+            "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==",
+            "dev": true,
+            "bin": {
+                "prettier": "bin-prettier.js"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/pump": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+            "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+            "dev": true,
+            "dependencies": {
+                "end-of-stream": "^1.1.0",
+                "once": "^1.3.1"
+            }
+        },
+        "node_modules/resolve-from": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+            "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/restore-cursor": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+            "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+            "dev": true,
+            "dependencies": {
+                "onetime": "^5.1.0",
+                "signal-exit": "^3.0.2"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/rxjs": {
+            "version": "6.6.3",
+            "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
+            "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
+            "dev": true,
+            "dependencies": {
+                "tslib": "^1.9.0"
+            },
+            "engines": {
+                "npm": ">=2.0.0"
+            }
+        },
+        "node_modules/semver-compare": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+            "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+            "dev": true
+        },
+        "node_modules/semver-regex": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz",
+            "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/shebang-command": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+            "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+            "dev": true,
+            "dependencies": {
+                "shebang-regex": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/shebang-regex": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+            "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/signal-exit": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+            "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+            "dev": true
+        },
+        "node_modules/slash": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+            "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/slice-ansi": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+            "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "astral-regex": "^2.0.0",
+                "is-fullwidth-code-point": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/string-argv": {
+            "version": "0.3.1",
+            "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
+            "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.6.19"
+            }
+        },
+        "node_modules/string-width": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+            "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+            "dev": true,
+            "dependencies": {
+                "emoji-regex": "^8.0.0",
+                "is-fullwidth-code-point": "^3.0.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/stringify-object": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+            "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+            "dev": true,
+            "dependencies": {
+                "get-own-enumerable-property-symbols": "^3.0.0",
+                "is-obj": "^1.0.1",
+                "is-regexp": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/strip-ansi": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+            "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+            "dev": true,
+            "dependencies": {
+                "ansi-regex": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/strip-final-newline": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+            "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/supports-color": {
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+            "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/through": {
+            "version": "2.3.8",
+            "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+            "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+            "dev": true
+        },
+        "node_modules/to-regex-range": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+            "dev": true,
+            "dependencies": {
+                "is-number": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=8.0"
+            }
+        },
+        "node_modules/tslib": {
+            "version": "1.14.1",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+            "dev": true
+        },
+        "node_modules/type-fest": {
+            "version": "0.11.0",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
+            "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/which": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+            "dev": true,
+            "dependencies": {
+                "isexe": "^2.0.0"
+            },
+            "bin": {
+                "node-which": "bin/node-which"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/which-pm-runs": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
+            "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=",
+            "dev": true
+        },
+        "node_modules/wrap-ansi": {
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+            "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/wrappy": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+            "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+            "dev": true
+        },
+        "node_modules/yaml": {
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
+            "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
+            "dev": true,
+            "engines": {
+                "node": ">= 6"
+            }
+        }
+    },
     "dependencies": {
         "@babel/code-frame": {
             "version": "7.10.4",
diff --git a/src/Protocol/Models/Position.Helpers.cs b/src/Protocol/Models/Position.Helpers.cs
new file mode 100644
index 000000000..d02aaee4a
--- /dev/null
+++ b/src/Protocol/Models/Position.Helpers.cs
@@ -0,0 +1,13 @@
+namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
+{
+    public partial class Position
+    {
+        /// 
+        /// Derive a new position from this position.
+        /// 
+        public Position Delta(int deltaLine = 0, int deltaCharacter = 0)
+        {
+            return new Position(Line + deltaLine, Character + deltaCharacter);
+        }
+    }
+}
diff --git a/src/Protocol/Models/Position.cs b/src/Protocol/Models/Position.cs
index 924d72def..53e73ca30 100644
--- a/src/Protocol/Models/Position.cs
+++ b/src/Protocol/Models/Position.cs
@@ -5,7 +5,7 @@
 namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
 {
     [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
-    public class Position : IEquatable, IComparable, IComparable
+    public partial  class Position : IEquatable, IComparable, IComparable
     {
         public Position()
         {
diff --git a/src/Protocol/Models/Range.Helpers.cs b/src/Protocol/Models/Range.Helpers.cs
new file mode 100644
index 000000000..812517692
--- /dev/null
+++ b/src/Protocol/Models/Range.Helpers.cs
@@ -0,0 +1,409 @@
+using System;
+using System.Collections.Generic;
+
+namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
+{
+
+    public partial class Range
+    {
+        /// 
+        /// Test if this range is empty.
+        /// 
+        public bool IsEmpty() => IsEmpty(this);
+
+        /// 
+        /// Test if `range` is empty.
+        /// 
+        public static bool IsEmpty(Range range) => range.Start.Line == range.End.Line && range.Start.Character == range.End.Character;
+
+        /// 
+        /// Test if position is in this range. If the position is at the edges, will return true.
+        /// 
+        public bool Contains(Position position) => ContainsPosition(this, position);
+
+        /// 
+        /// Test if `position` is in `range`. If the position is at the edges, will return true.
+        /// 
+        public static bool ContainsPosition(Range range, Position position)
+        {
+            if (position.Line < range.Start.Line || position.Line > range.End.Line)
+            {
+                return false;
+            }
+
+            if (position.Line == range.Start.Line && position.Character < range.Start.Character)
+            {
+                return false;
+            }
+
+            if (position.Line == range.End.Line && position.Character > range.End.Character)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// 
+        /// Test if range is in this range. If the range is equal to this range, will return true.
+        /// 
+        public bool Contains(Range range) => ContainsRange(this, range);
+
+        /// 
+        /// Test if `otherRange` is in `range`. If the ranges are equal, will return true.
+        /// 
+        public static bool ContainsRange(Range range, Range otherRange)
+        {
+            if (otherRange.Start.Line < range.Start.Line || otherRange.End.Line < range.Start.Line)
+            {
+                return false;
+            }
+
+            if (otherRange.Start.Line > range.End.Line || otherRange.End.Line > range.End.Line)
+            {
+                return false;
+            }
+
+            if (otherRange.Start.Line == range.Start.Line && otherRange.Start.Character < range.Start.Character)
+            {
+                return false;
+            }
+
+            if (otherRange.End.Line == range.End.Line && otherRange.End.Character > range.End.Character)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// 
+        /// Test if `range` is strictly in this range. `range` must start after and end before this range for the result to be true.
+        /// 
+        public bool StrictContains(Range range) => StrictContainsRange(this, range);
+
+        /// 
+        /// Test if `otherRange` is strictly in `range` (must start after, and end before). If the ranges are equal, will return false.
+        /// 
+        public static bool StrictContainsRange(Range range, Range otherRange)
+        {
+            if (otherRange.Start.Line < range.Start.Line || otherRange.End.Line < range.Start.Line)
+            {
+                return false;
+            }
+
+            if (otherRange.Start.Line > range.End.Line || otherRange.End.Line > range.End.Line)
+            {
+                return false;
+            }
+
+            if (otherRange.Start.Line == range.Start.Line && otherRange.Start.Character <= range.Start.Character)
+            {
+                return false;
+            }
+
+            if (otherRange.End.Line == range.End.Line && otherRange.End.Character >= range.End.Character)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// 
+        /// A reunion of the two ranges.
+        /// The smallest position will be used as the start point, and the largest one as the end point.
+        /// 
+        public static Range operator +(Range a, Range b)
+        {
+            return PlusRange(a, b);
+        }
+
+        /// 
+        /// A reunion of the two ranges.
+        /// The smallest position will be used as the start point, and the largest one as the end point.
+        /// 
+        public static Range PlusRange(Range a, Range b)
+        {
+            int startLineNumber;
+            int startColumn;
+            int endLineNumber;
+            int endColumn;
+
+            if (b.Start.Line < a.Start.Line)
+            {
+                startLineNumber = b.Start.Line;
+                startColumn = b.Start.Character;
+            }
+            else if (b.Start.Line == a.Start.Line)
+            {
+                startLineNumber = b.Start.Line;
+                startColumn = Math.Min(b.Start.Character, a.Start.Character);
+            }
+            else
+            {
+                startLineNumber = a.Start.Line;
+                startColumn = a.Start.Character;
+            }
+
+            if (b.End.Line > a.End.Line)
+            {
+                endLineNumber = b.End.Line;
+                endColumn = b.End.Character;
+            }
+            else if (b.End.Line == a.End.Line)
+            {
+                endLineNumber = b.End.Line;
+                endColumn = Math.Max(b.End.Character, a.End.Character);
+            }
+            else
+            {
+                endLineNumber = a.End.Line;
+                endColumn = a.End.Character;
+            }
+
+            return new Range(( startLineNumber, startColumn ), ( endLineNumber, endColumn ));
+        }
+
+        /// 
+        /// A intersection of the two ranges.
+        /// 
+        public Range? Intersection(Range other)
+        {
+            return Intersection(this, other);
+        }
+
+        /// 
+        /// A intersection of the two ranges.
+        /// 
+        public static Range? Intersection(Range a, Range b)
+        {
+            var resultStartLineNumber = a.Start.Line;
+            var resultStartColumn = a.Start.Character;
+            var resultEndLineNumber = a.End.Line;
+            var resultEndColumn = a.End.Character;
+            var otherStartLineNumber = b.Start.Line;
+            var otherStartColumn = b.Start.Character;
+            var otherEndLineNumber = b.End.Line;
+            var otherEndColumn = b.End.Character;
+
+            if (resultStartLineNumber < otherStartLineNumber)
+            {
+                resultStartLineNumber = otherStartLineNumber;
+                resultStartColumn = otherStartColumn;
+            }
+            else if (resultStartLineNumber == otherStartLineNumber)
+            {
+                resultStartColumn = Math.Max(resultStartColumn, otherStartColumn);
+            }
+
+            if (resultEndLineNumber > otherEndLineNumber)
+            {
+                resultEndLineNumber = otherEndLineNumber;
+                resultEndColumn = otherEndColumn;
+            }
+            else if (resultEndLineNumber == otherEndLineNumber)
+            {
+                resultEndColumn = Math.Min(resultEndColumn, otherEndColumn);
+            }
+
+            // Check if selection is now empty
+            if (resultStartLineNumber > resultEndLineNumber)
+            {
+                return null;
+            }
+
+            if (resultStartLineNumber == resultEndLineNumber && resultStartColumn > resultEndColumn)
+            {
+                return null;
+            }
+
+            return new Range(( resultStartLineNumber, resultStartColumn ), ( resultEndLineNumber, resultEndColumn ));
+        }
+
+        /// 
+        /// Create a new empty range using this range's start position.
+        /// 
+        public Range CollapseToStart() => CollapseToStart(this);
+
+        /// 
+        /// Create a new empty range using this range's start position.
+        /// 
+        public static Range CollapseToStart(Range range) => new Range(range.Start, range.Start);
+
+        /// 
+        /// Create a new empty range using this range's start position.
+        /// 
+        public Range CollapseToEnd() => CollapseToEnd(this);
+
+        /// 
+        /// Create a new empty range using this range's start position.
+        /// 
+        public static Range CollapseToEnd(Range range) => new Range(range.End, range.End);
+
+        public static bool IsBefore(Range a, Range b) => a.End.Line < b.Start.Line || a.End.Line == b.Start.Line && a.End.Character < b.Start.Character;
+
+        public bool IsBefore(Range other) => IsBefore(this, other);
+
+        public static bool IsBeforeOrTouching(Range a, Range b) => a.End.Line < b.Start.Line || a.End.Line == b.Start.Line && a.End.Character <= b.Start.Character;
+
+        public bool IsBeforeOrTouching(Range other) => IsBeforeOrTouching(this, other);
+
+        public static bool IsAfter(Range a, Range b) => b.End.Line < a.Start.Line || b.End.Line == a.Start.Line && b.End.Character < a.Start.Character;
+
+        public bool IsAfter(Range other) => IsAfter(this, other);
+
+        public static bool IsAfterOrTouching(Range a, Range b) => b.End.Line < a.Start.Line || b.End.Line == a.Start.Line && b.End.Character <= a.Start.Character;
+
+        public bool IsAfterOrTouching(Range other) => IsAfterOrTouching(this, other);
+
+        /// 
+        /// Test if the two ranges are touching in any way. If the ranges are touching it returns false.
+        /// 
+        public static bool AreIntersectingOrTouching(Range a, Range b)
+        {
+            // Check if `a` is before `b`
+            if (IsBefore(a, b))
+            {
+                return false;
+            }
+
+            // Check if `b` is before `a`
+            if (IsAfter(a, b))
+            {
+                return false;
+            }
+
+            // These ranges must intersect
+            return true;
+        }
+
+        public bool IntersectsOrTouches(Range other) => AreIntersectingOrTouching(this, other);
+
+        /// 
+        /// Test if the two ranges are intersecting. If the ranges are touching it returns false.
+        /// 
+        public bool Intersects(Range other)
+        {
+            return AreIntersecting(this, other);
+        }
+
+        /// 
+        /// Test if the two ranges are intersecting. If the ranges are touching it returns false.
+        /// 
+        public static bool AreIntersecting(Range a, Range b)
+        {
+            // Check if `a` is before `b`
+            if (IsBeforeOrTouching(a, b))
+            {
+                return false;
+            }
+
+            // Check if `b` is before `a`
+            if (IsAfterOrTouching(a, b))
+            {
+                return false;
+            }
+
+            // These ranges must intersect
+            return true;
+        }
+
+        public static IComparer AscendingComparer { get; } = new StartPositionComparer();
+        public static IComparer CompareUsingStarts => AscendingComparer;
+        public static IComparer DescendingComparer { get; } = new EndPositionComparer();
+        public static IComparer CompareUsingEnds => DescendingComparer;
+
+        public class StartPositionComparer : IComparer
+        {
+            public int Compare(Range x, Range y) => CompareRangesUsingStarts(x, y);
+        }
+
+        public class EndPositionComparer : IComparer
+        {
+            public int Compare(Range x, Range y) => CompareRangesUsingEnds(x, y);
+        }
+
+        /// 
+        /// A function that compares ranges, useful for sorting ranges
+        /// It will first compare ranges on the startPosition and then on the endPosition
+        /// 
+        public static int CompareRangesUsingStarts(Range? a, Range? b)
+        {
+            if (a is not null && b is not null)
+            {
+                var aStartLineNumber = a.Start.Line | 0;
+                var bStartLineNumber = b.Start.Line | 0;
+
+                if (aStartLineNumber == bStartLineNumber)
+                {
+                    var aStartColumn = a.Start.Character | 0;
+                    var bStartColumn = b.Start.Character | 0;
+
+                    if (aStartColumn == bStartColumn)
+                    {
+                        var aEndLineNumber = a.End.Line | 0;
+                        var bEndLineNumber = b.End.Line | 0;
+
+                        if (aEndLineNumber == bEndLineNumber)
+                        {
+                            var aEndColumn = a.End.Character | 0;
+                            var bEndColumn = b.End.Character | 0;
+                            return aEndColumn - bEndColumn;
+                        }
+
+                        return aEndLineNumber - bEndLineNumber;
+                    }
+
+                    return aStartColumn - bStartColumn;
+                }
+
+                return aStartLineNumber - bStartLineNumber;
+            }
+
+            var aExists = a is not null ? 1 : 0;
+            var bExists = b is not null ? 1 : 0;
+            return aExists - bExists;
+        }
+
+        /// 
+        /// A function that compares ranges, useful for sorting ranges
+        /// It will first compare ranges on the endPosition and then on the startPosition
+        /// 
+        public static int CompareRangesUsingEnds(Range a, Range b)
+        {
+            if (a.End.Line == b.End.Line)
+            {
+                if (a.End.Character == b.End.Character)
+                {
+                    if (a.Start.Line == b.Start.Line)
+                    {
+                        return a.Start.Character - b.Start.Character;
+                    }
+
+                    return a.Start.Line - b.Start.Line;
+                }
+
+                return a.End.Character - b.End.Character;
+            }
+
+            return a.End.Line - b.End.Line;
+        }
+
+        /// 
+        /// Test if the range spans multiple lines.
+        /// 
+        public static bool SpansMultipleLines(Range range)
+        {
+            return range.End.Line > range.Start.Line;
+        }
+
+        /// 
+        /// Test if the range spans multiple lines.
+        /// 
+        public bool SpansMultipleLines()
+        {
+            return SpansMultipleLines(this);
+        }
+    }
+}
diff --git a/src/Protocol/Models/Range.cs b/src/Protocol/Models/Range.cs
index 4ee12bdfe..bfa376400 100644
--- a/src/Protocol/Models/Range.cs
+++ b/src/Protocol/Models/Range.cs
@@ -5,7 +5,7 @@
 namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
 {
     [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
-    public class Range : IEquatable
+    public partial class Range : IEquatable
     {
         public Range()
         {
@@ -17,6 +17,12 @@ public Range(Position start, Position end)
             End = end;
         }
 
+        public Range(int startLine, int startCharacter, int endLine, int endCharacter)
+        {
+            Start = ( startLine, startCharacter );
+            End = ( endLine, endCharacter );
+        }
+
         /// 
         /// The range's start position.
         /// 
@@ -48,7 +54,7 @@ public override int GetHashCode()
 
         public static implicit operator Range((Position start, Position end) value) => new Range(value.start, value.end);
 
-        private string DebuggerDisplay => $"[start: {Start}, end: {End}]";
+        private string DebuggerDisplay => $"[start: ({Start?.Line}, {Start?.Character}), end: ({End?.Line}, {End?.Character})]";
 
         /// 
         public override string ToString() => DebuggerDisplay;
diff --git a/src/Testing/PositionMarker.cs b/src/Testing/PositionMarker.cs
new file mode 100644
index 000000000..4f20e0ff7
--- /dev/null
+++ b/src/Testing/PositionMarker.cs
@@ -0,0 +1,73 @@
+using System;
+
+namespace OmniSharp.Extensions.LanguageProtocol.Testing
+{
+    public readonly struct PositionMarker : IEquatable, IComparable, IComparable
+    {
+        public char First { get; }
+        public char Second { get; }
+        public int CompareTo(PositionMarker other)
+        {
+            var firstComparison = First.CompareTo(other.First);
+            if (firstComparison != 0) return firstComparison;
+            return Second.CompareTo(other.Second);
+        }
+
+        public int CompareTo(object? obj)
+        {
+            if (ReferenceEquals(null, obj)) return 1;
+            return obj is PositionMarker other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(PositionMarker)}");
+        }
+
+        public static bool operator <(PositionMarker left, PositionMarker right) => left.CompareTo(right) < 0;
+
+        public static bool operator >(PositionMarker left, PositionMarker right) => left.CompareTo(right) > 0;
+
+        public static bool operator <=(PositionMarker left, PositionMarker right) => left.CompareTo(right) <= 0;
+
+        public static bool operator >=(PositionMarker left, PositionMarker right) => left.CompareTo(right) >= 0;
+
+        public bool Equals(PositionMarker other) => First == other.First && Second == other.Second;
+
+        public override bool Equals(object? obj) => obj is PositionMarker other && Equals(other);
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return ( First.GetHashCode() * 397 ) ^ Second.GetHashCode();
+            }
+        }
+
+        public static bool operator ==(PositionMarker left, PositionMarker right) => left.Equals(right);
+
+        public static bool operator !=(PositionMarker left, PositionMarker right) => !left.Equals(right);
+
+
+        public PositionMarker(char first, char second)
+        {
+            First = first;
+            Second = second;
+        }
+
+        public PositionMarker(string value)
+        {
+            if (value.Length != 2) throw new ArgumentOutOfRangeException(nameof(value), value, "Expected a string with 2 characters");
+            First = value[0];
+            Second = value[1];
+        }
+
+        public void Deconstruct(out char first, out char second)
+        {
+            first = First;
+            second = Second;
+        }
+
+        public static implicit operator PositionMarker((char first, char second) value)
+        {
+            return new PositionMarker(value.first, value.second);
+        }
+
+        public override string ToString() => $"{First}{Second}";
+    }
+}
\ No newline at end of file
diff --git a/src/Testing/TestConfigurationProvider.cs b/src/Testing/TestConfigurationProvider.cs
index 68956c441..13da4030a 100644
--- a/src/Testing/TestConfigurationProvider.cs
+++ b/src/Testing/TestConfigurationProvider.cs
@@ -160,7 +160,7 @@ private T SetValueToToken(JToken root, string key, T value)
                 return (T) arr2[i];
             }
 
-            return (root[key] as T)!;
+            return ( root[key] as T )!;
         }
 
         private static JToken? GetValueFromToken(JToken root, string key)
diff --git a/src/Testing/TestContent.cs b/src/Testing/TestContent.cs
new file mode 100644
index 000000000..a9c52d607
--- /dev/null
+++ b/src/Testing/TestContent.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
+
+namespace OmniSharp.Extensions.LanguageProtocol.Testing
+{
+    /// 
+    /// MarkupCode allows encoding additional pieces of information along with a piece of source code
+    /// that are useful for testing. The following information can be encoded:
+    ///
+    /// $$ - The index in the code. There can be no more than one of these.
+    ///
+    /// [| ... |] - A span in the code. There can be many of these and they can be nested.
+    ///
+    /// {|Name| ... |} - A span of code that is annotated with a name. There can be many of these and
+    /// they can be nested.
+    ///
+    /// This is similar the MarkupTestFile used in Roslyn:
+    ///     https://github.com/dotnet/roslyn/blob/master/src/Test/Utilities/Shared/MarkedSource/MarkupTestFile.cs
+    /// 
+    /// 
+    /// Taken from OmniSharp source and modified
+    /// 
+    public class TestContent
+    {
+        private readonly int? _index;
+        private readonly ImmutableDictionary> _spans;
+
+        private TestContent(string code, int? index, ImmutableDictionary> spans)
+        {
+            Code = code;
+            Lines = ParseLines(code).ToImmutableArray();
+            _index = index;
+            _spans = spans;
+        }
+
+        public string Code { get; }
+        public ImmutableArray Lines { get; }
+        public int Index => _index ?? -1;
+        public bool HasIndex => _index.HasValue;
+
+        public ImmutableList GetRanges(string? name = null)
+        {
+            if (_spans.TryGetValue(name ?? string.Empty, out var result))
+            {
+                return result;
+            }
+
+            return ImmutableList.Empty;
+        }
+
+        public Position GetPositionAtIndex(int? index = null) => TestSourceHelpers.GetPositionAtIndex(Code, index ?? Index);
+        public int GetIndexAtPosition(Position position) => TestSourceHelpers.GetIndexAtPosition(Lines, position);
+
+        private static IEnumerable ParseLines(string source)
+        {
+            var lastStart = 0;
+            var length = source.Length;
+            for (var index = 0; index < length; index++)
+            {
+                if (source[index] == '\n')
+                {
+                    yield return source.Substring(lastStart, Math.Min(index + 1, length) - lastStart);
+                    lastStart = index + 1;
+                }
+            }
+
+            yield return source.Substring(lastStart);
+        }
+
+        public static TestContent Parse(string input, TestContentOptions? options = null)
+        {
+            options ??= new TestContentOptions();
+            // TODO: Should this be configurable?
+            input = input.NormalizeLineEndings();
+            var markupLength = input.Length;
+            var codeBuilder = new StringBuilder(markupLength);
+
+            int? position = null;
+            var spanStartStack = new Stack();
+            var namedSpanStartStack = new Stack<(int spanStart, string spanName)>();
+            var spans = new Dictionary>();
+
+            var codeIndex = 0;
+            var markupIndex = 0;
+
+            var positionMarker = options.PositionMarker;
+            var rangeMarker = options.RangeMarker;
+            var namedMarker = options.NamedRangeMarker;
+
+            while (markupIndex < markupLength)
+            {
+                var ch = input[markupIndex];
+
+                if (ch == positionMarker.First)
+                {
+                    if (position == null &&
+                        markupIndex + 1 < markupLength &&
+                        input[markupIndex + 1] == positionMarker.Second)
+                    {
+                        position = codeIndex;
+                        markupIndex += 2;
+                        continue;
+                    }
+                }
+                else if (ch == rangeMarker.open.First)
+                {
+                    if (markupIndex + 1 < markupLength &&
+                        input[markupIndex + 1] == rangeMarker.open.Second)
+                    {
+                        spanStartStack.Push(codeIndex);
+                        markupIndex += 2;
+                        continue;
+                    }
+                }
+                else if (ch == namedMarker.open.First)
+                {
+                    if (markupIndex + 1 < markupLength &&
+                        input[markupIndex + 1] == namedMarker.open.Second)
+                    {
+                        var nameIndex = markupIndex + 2;
+                        var nameStartIndex = nameIndex;
+                        var nameLength = 0;
+                        var found = false;
+
+                        // Parse out name
+                        while (nameIndex < markupLength)
+                        {
+                            if (input[nameIndex] == namedMarker.labelStop)
+                            {
+                                found = true;
+                                break;
+                            }
+
+                            nameLength++;
+                            nameIndex++;
+                        }
+
+                        if (found)
+                        {
+                            var name = input.Substring(nameStartIndex, nameLength);
+                            namedSpanStartStack.Push(( codeIndex, name ));
+                            markupIndex = nameIndex + 1; // Move after ':'
+                            continue;
+                        }
+
+                        // We didn't find a ':'. In this case, we just carry on...
+                    }
+                }
+                else if (ch == rangeMarker.close.First || ch == namedMarker.close.First)
+                {
+                    if (markupIndex + 1 < markupLength)
+                    {
+                        if (ch == rangeMarker.close.First && input[markupIndex + 1] == rangeMarker.close.Second)
+                        {
+                            if (spanStartStack.Count == 0)
+                            {
+                                throw new ArgumentException($"Saw {rangeMarker.close} without matching {rangeMarker.open}");
+                            }
+
+                            var spanStart = spanStartStack.Pop();
+
+                            AddSpan(spans, string.Empty, spanStart, codeIndex);
+                            markupIndex += 2;
+
+                            continue;
+                        }
+
+                        if (ch == namedMarker.close.First && input[markupIndex + 1] == namedMarker.close.Second)
+                        {
+                            if (namedSpanStartStack.Count == 0)
+                            {
+                                throw new ArgumentException($"Saw {namedMarker.close} without matching {namedMarker.open}");
+                            }
+
+                            var tuple = namedSpanStartStack.Pop();
+                            var spanStart = tuple.Item1;
+                            var spanName = tuple.Item2;
+
+                            AddSpan(spans, spanName, spanStart, codeIndex);
+                            markupIndex += 2;
+
+                            continue;
+                        }
+                    }
+                }
+
+                codeBuilder.Append(ch);
+                codeIndex++;
+                markupIndex++;
+            }
+
+            var source = codeBuilder.ToString();
+            var finalSpans = spans
+               .ToImmutableDictionary(
+                    keySelector: kvp => kvp.Key,
+                    elementSelector: kvp => kvp.Value
+                                               .Select(z => new Range(TestSourceHelpers.GetPositionAtIndex(source, z.start), TestSourceHelpers.GetPositionAtIndex(source, z.end)))
+                                               .ToImmutableList()
+                                               .Sort(Range.AscendingComparer)
+                );
+
+            return new TestContent(source, position, finalSpans);
+        }
+
+        private static void AddSpan(Dictionary> spans, string spanName, int spanStart, int spanEnd)
+        {
+            if (!spans.TryGetValue(spanName, out var spanList))
+            {
+                spanList = new List<(int start, int end)>();
+                spans.Add(spanName, spanList);
+            }
+
+            spanList.Add(( spanStart, spanEnd ));
+        }
+    }
+}
diff --git a/src/Testing/TestContentOptions.cs b/src/Testing/TestContentOptions.cs
new file mode 100644
index 000000000..e0cbe11a8
--- /dev/null
+++ b/src/Testing/TestContentOptions.cs
@@ -0,0 +1,9 @@
+namespace OmniSharp.Extensions.LanguageProtocol.Testing
+{
+    public class TestContentOptions
+    {
+        public PositionMarker PositionMarker { get; set; } = ( '$', '$' );
+        public (PositionMarker open, char labelStop, PositionMarker close) NamedRangeMarker { get; set; } = ( ('{', '|'), ':', ('|', '}') );
+        public (PositionMarker open, PositionMarker close) RangeMarker { get; set; } = ( ('[', '|'), ('|', ']') );
+    }
+}
\ No newline at end of file
diff --git a/src/Testing/TestSourceHelpers.cs b/src/Testing/TestSourceHelpers.cs
new file mode 100644
index 000000000..5b3e588ec
--- /dev/null
+++ b/src/Testing/TestSourceHelpers.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
+
+namespace OmniSharp.Extensions.LanguageProtocol.Testing
+{
+    public static class TestSourceHelpers
+    {
+        public static Position GetPositionAtIndex(string source, int index)
+        {
+            var line = 0;
+            var span = source.AsSpan();
+            var rollingIndex = 0;
+            do
+            {
+                var location = span.IndexOf('\n');
+                if (location == -1)
+                {
+                    if (rollingIndex + span.Length >= index)
+                    {
+                        return new Position(line, index - rollingIndex);
+                    }
+
+                    return ( line, span.Length );
+                }
+
+                if (rollingIndex + location >= index)
+                {
+                    return new Position(line, index - rollingIndex);
+                }
+
+                span = span.Slice(location + 1);
+                rollingIndex += location + 1;
+                line++;
+                if (rollingIndex == index)
+                {
+                    return new Position(line, 0);
+                }
+            } while (!span.IsEmpty);
+
+            return ( line, 0 );
+        }
+
+        public static string NormalizeLineEndings(this string value) => value.Replace("\r\n", "\n");
+
+        public static string ExtractRange(this TestContent source, Range range)
+        {
+            var start = source.GetIndexAtPosition(range.Start);
+            return source.Code.Substring(start, source.GetIndexAtPosition(range.End) - start);
+        }
+
+        public static IEnumerable ExtractRanges(this TestContent source, IEnumerable ranges)
+        {
+            foreach (var range in ranges)
+            {
+                var start = source.GetIndexAtPosition(range.Start);
+                yield return source.Code.Substring(start, source.GetIndexAtPosition(range.End) - start);
+            }
+        }
+
+        public static int GetIndexAtPosition(IReadOnlyList lines, Position position)
+        {
+            if (position.Line >= lines.Count) return -1;
+            var characterCount = lines
+                                .Take(position.Line)
+                                .Aggregate(0, (acc, v) => acc + v.Length);
+            return characterCount + position.Character;
+        }
+
+        public static int GetIndexAtPosition(in string[] lines, Position position)
+        {
+            if (position.Line >= lines.Length) return -1;
+            var characterCount = lines
+                                .Take(position.Line)
+                                .Aggregate(0, (acc, v) => acc + v.Length);
+            return characterCount + position.Character;
+        }
+    }
+}
diff --git a/test/Lsp.Tests/Models/PositionTests.cs b/test/Lsp.Tests/Models/PositionTests.cs
index a869f3b4a..cb743abb3 100644
--- a/test/Lsp.Tests/Models/PositionTests.cs
+++ b/test/Lsp.Tests/Models/PositionTests.cs
@@ -52,5 +52,17 @@ public void Is_Sortable_By_Line()
             a.Should().BeLessOrEqualTo(c);
             a.Should().BeGreaterOrEqualTo(c);
         }
+
+        [Fact]
+        public void Should_Support_Delta()
+        {
+            var a = new Position(1, 1);
+            a = a.Delta(deltaLine: 1);
+            a.Line.Should().Be(2);
+            a = a.Delta(deltaCharacter: -1);
+            a.Character.Should().Be(0);
+            a = a.Delta(-1, 1);
+            a.Should().Be(( 1, 1 ));
+        }
     }
 }
diff --git a/test/Lsp.Tests/Models/RangeTests.cs b/test/Lsp.Tests/Models/RangeTests.cs
index 17e63b385..fcbec3734 100644
--- a/test/Lsp.Tests/Models/RangeTests.cs
+++ b/test/Lsp.Tests/Models/RangeTests.cs
@@ -1,3 +1,4 @@
+using System.Linq;
 using FluentAssertions;
 using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
 using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -20,5 +21,259 @@ public void SimpleTest(string expected)
             var deresult = new Serializer(ClientVersion.Lsp3).DeserializeObject(expected);
             deresult.Should().BeEquivalentTo(model);
         }
+
+        [Fact]
+        public void Range_Is_Empty()
+        {
+            var s = new Range(1, 1, 1, 1);
+            s.IsEmpty().Should().BeTrue();
+        }
+
+        [Fact]
+        public void Range_Equality()
+        {
+            var a = new Range(1, 1, 1, 1);
+            var b = new Range(1, 1, 1, 1);
+            var c = new Range(1, 1, 1, 2);
+            a.Should().Be(b);
+            a.Should().NotBe(c);
+        }
+
+        [Theory]
+        [InlineData(1, 2, 1, 1)]
+        [InlineData(2, 1, 1, 2)]
+        [InlineData(1, 1, 1, 2)]
+        [InlineData(1, 1, 2, 1)]
+        public void Range_Is_Not_Empty(int startLine, int startCharacter, int endLine, int endCharacter)
+        {
+            var s = new Range(startLine, startCharacter, endLine, endCharacter);
+            s.IsEmpty().Should().BeFalse();
+        }
+
+        [Theory]
+        [InlineData(1, 1, 1, 3, 1, 2, 1, 4, "lt", "a.start < b.start, a.end < b.end")]
+        [InlineData(1, 1, 1, 3, 1, 1, 1, 4, "lt", "a.start = b.start, a.end < b.end")]
+        [InlineData(1, 2, 1, 3, 1, 1, 1, 4, "lt", "a.start > b.start, a.end < b.end")]
+        [InlineData(1, 1, 1, 4, 1, 2, 1, 4, "lt", "a.start < b.start, a.end = b.end")]
+        [InlineData(1, 1, 1, 4, 1, 1, 1, 4, "eq", "a.start = b.start, a.end = b.end")]
+        [InlineData(1, 2, 1, 4, 1, 1, 1, 4, "gt", "a.start > b.start, a.end = b.end")]
+        [InlineData(1, 1, 1, 5, 1, 2, 1, 4, "gt", "a.start < b.start, a.end > b.end")]
+        [InlineData(1, 1, 2, 4, 1, 1, 1, 4, "gt", "a.start = b.start, a.end > b.end")]
+        [InlineData(1, 2, 5, 1, 1, 1, 1, 4, "gt", "a.start > b.start, a.end > b.end")]
+        public void Compare_Ranges_Using_Ends(int startLineA, int startCharacterA, int endLineA, int endCharacterA, int startLineB, int startCharacterB, int endLineB, int endCharacterB, string @operator, string because)
+        {
+            var a = new Range(startLineA, startCharacterA, endLineA, endCharacterA);
+            var b = new Range(startLineB, startCharacterB, endLineB, endCharacterB);
+
+            ( @operator switch {
+                "lt" => Range.CompareRangesUsingEnds(a, b) < 0,
+                "eq" => Range.CompareRangesUsingEnds(a, b) == 0,
+                "gt" => Range.CompareRangesUsingEnds(a, b) > 0,
+                _    => false
+            } ).Should().BeTrue(because);
+
+        }
+
+        [Theory]
+        [InlineData(1, 1, 1, 3, 1, 2, 1, 4, "lt", "a.start < b.start, a.end < b.end")]
+        [InlineData(1, 1, 1, 5, 1, 2, 1, 4, "lt", "a.start < b.start, a.end > b.end")]
+        [InlineData(1, 1, 1, 4, 1, 2, 1, 4, "lt", "a.start < b.start, a.end = b.end")]
+        [InlineData(1, 1, 1, 3, 1, 1, 1, 4, "lt", "a.start = b.start, a.end < b.end")]
+        [InlineData(1, 1, 1, 4, 1, 1, 1, 4, "eq", "a.start = b.start, a.end = b.end")]
+        [InlineData(1, 1, 2, 4, 1, 1, 1, 4, "gt", "a.start = b.start, a.end > b.end")]
+        [InlineData(1, 2, 1, 4, 1, 1, 1, 4, "gt", "a.start > b.start, a.end = b.end")]
+        [InlineData(1, 2, 1, 3, 1, 1, 1, 4, "gt", "a.start > b.start, a.end < b.end")]
+        [InlineData(1, 2, 5, 1, 1, 1, 1, 4, "gt", "a.start > b.start, a.end > b.end")]
+        public void Compare_Ranges_Using_Starts(int startLineA, int startCharacterA, int endLineA, int endCharacterA, int startLineB, int startCharacterB, int endLineB, int endCharacterB, string @operator, string because)
+        {
+            var a = new Range(startLineA, startCharacterA, endLineA, endCharacterA);
+            var b = new Range(startLineB, startCharacterB, endLineB, endCharacterB);
+
+            ( @operator switch {
+                "lt" => Range.CompareRangesUsingStarts(a, b) < 0,
+                "eq" => Range.CompareRangesUsingStarts(a, b) == 0,
+                "gt" => Range.CompareRangesUsingStarts(a, b) > 0,
+                _    => false
+            } ).Should().BeTrue(because);
+
+        }
+
+        [Theory]
+        [InlineData(2, 2, 5, 10, 1, 3, false)]
+        [InlineData(2, 2, 5, 10, 2, 1, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, true)]
+        [InlineData(2, 2, 5, 10, 2, 3, true)]
+        [InlineData(2, 2, 5, 10, 3, 1, true)]
+        [InlineData(2, 2, 5, 10, 5, 9, true)]
+        [InlineData(2, 2, 5, 10, 5, 10, true)]
+        [InlineData(2, 2, 5, 10, 5, 11, false)]
+        [InlineData(2, 2, 5, 10, 6, 1, false)]
+        public void Range_Contains_Position(int startLine, int startCharacter, int endLine, int endCharacter, int positionLine, int positionCharacter, bool result)
+        {
+            var range = new Range(startLine, startCharacter, endLine, endCharacter);
+            range.Contains(new Position(positionLine, positionCharacter)).Should().Be(result);
+        }
+
+        [Theory]
+        [InlineData(2, 2, 5, 10, 1, 3, 2, 2, false)]
+        [InlineData(2, 2, 5, 10, 2, 1, 2, 2, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, 5, 11, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, 6, 1, false)]
+        [InlineData(2, 2, 5, 10, 5, 9, 6, 1, false)]
+        [InlineData(2, 2, 5, 10, 5, 10, 6, 1, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, 5, 10, true)]
+        [InlineData(2, 2, 5, 10, 2, 3, 5, 9, true)]
+        [InlineData(2, 2, 5, 10, 3, 100, 4, 100, true)]
+        public void Range_Contains_Range(int startLine, int startCharacter, int endLine, int endCharacter, int startLineB, int startCharacterB, int endLineB, int endCharacterB, bool result)
+        {
+            var rangeA = new Range(startLine, startCharacter, endLine, endCharacter);
+            var rangeB = new Range(startLineB, startCharacterB, endLineB, endCharacterB);
+            rangeA.Contains(rangeB).Should().Be(result);
+        }
+
+        [Theory]
+        [InlineData(2, 2, 5, 10, 1, 3, 2, 2, false)]
+        [InlineData(2, 2, 5, 10, 2, 1, 2, 2, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, 5, 11, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, 6, 1, false)]
+        [InlineData(2, 2, 5, 10, 5, 9, 6, 1, false)]
+        [InlineData(2, 2, 5, 10, 5, 10, 6, 1, false)]
+        [InlineData(2, 2, 5, 9, 2, 2, 5, 10, false)]
+        [InlineData(2, 2, 5, 10, 2, 2, 5, 10, false)]
+        [InlineData(2, 1, 5, 10, 2, 2, 5, 10, false)]
+        [InlineData(2, 2, 5, 10, 2, 3, 5, 9, true)]
+        [InlineData(2, 2, 5, 10, 3, 100, 4, 100, true)]
+        public void Range_Strictly_Contains_Range(int startLine, int startCharacter, int endLine, int endCharacter, int startLineB, int startCharacterB, int endLineB, int endCharacterB, bool result)
+        {
+            var rangeA = new Range(startLine, startCharacter, endLine, endCharacter);
+            var rangeB = new Range(startLineB, startCharacterB, endLineB, endCharacterB);
+            rangeA.StrictContains(rangeB).Should().Be(result);
+        }
+
+        [Theory]
+        [InlineData(2, 2, 3, 2, 4, 2, 5, 2, false)]
+        [InlineData(4, 2, 5, 2, 2, 2, 3, 2, false)]
+        [InlineData(4, 2, 5, 2, 5, 2, 6, 2, false)]
+        [InlineData(5, 2, 6, 2, 4, 2, 5, 2, false)]
+        [InlineData(2, 2, 2, 7, 2, 4, 2, 6, true)]
+        [InlineData(2, 2, 2, 7, 2, 4, 2, 9, true)]
+        [InlineData(2, 4, 2, 9, 2, 2, 2, 7, true)]
+        public void Range_Are_Intersecting(int startLine, int startCharacter, int endLine, int endCharacter, int startLineB, int startCharacterB, int endLineB, int endCharacterB, bool result)
+        {
+            var rangeA = new Range(startLine, startCharacter, endLine, endCharacter);
+            var rangeB = new Range(startLineB, startCharacterB, endLineB, endCharacterB);
+            Range.AreIntersecting(rangeA, rangeB).Should().Be(result);
+        }
+
+        [Fact]
+        public void Ranges_Can_Be_Added()
+        {
+            var a = new Range(1, 1, 2, 2);
+            var b = new Range(3,3, 4,0);
+
+            ( a + b ).Should().Be(new Range(1, 1, 4, 0));
+        }
+
+        [Fact]
+        public void Ranges_Can_Span_Lines()
+        {
+            var a = new Range(1, 1, 1, 2);
+            var b = new Range(3,3, 4,0);
+
+            a.SpansMultipleLines().Should().BeFalse();
+            b.SpansMultipleLines().Should().BeTrue();
+        }
+
+        [Fact]
+        public void Ranges_Can_Collapse()
+        {
+            var a = new Range(1, 1, 2, 2);
+
+            a.CollapseToStart().Should().Be(new Range(a.Start, a.Start));
+            a.CollapseToEnd().Should().Be(new Range(a.End, a.End));
+        }
+
+        [Fact]
+        public void Ranges_Can_Be_Intersected()
+        {
+            var a = new Range(1, 1, 1, 3);
+            var b = new Range(2, 1, 2, 2);
+            var c = new Range(1,2, 4, 0);
+
+            a.Intersection(b).Should().BeNull();
+            b.Intersection(a).Should().BeNull();
+            a.Intersection(a).Should().Be(a);
+
+            c.Intersection(b).Should().Be(b);
+            b.Intersection(c).Should().Be(b);
+            c.Intersection(a).Should().Be(new Range(1,2, 1, 3));
+            a.Intersection(c).Should().Be(new Range(1,2, 1, 3));
+        }
+
+        [Fact]
+        public void Ranges_Compare_Compare_With_Or_Without_Touching()
+        {
+            var a = new Range(1, 1, 1, 3);
+            var b = new Range(2, 1, 2, 3);
+            var c = new Range(1, 3, 2, 1);
+
+            a.IsBefore(b).Should().BeTrue();
+            b.IsAfter(a).Should().BeTrue();
+            a.IsBefore(c).Should().BeFalse();
+            b.IsAfter(c).Should().BeFalse();
+
+            a.IsBeforeOrTouching(c).Should().BeTrue();
+            b.IsAfterOrTouching(c).Should().BeTrue();
+        }
+
+        [Fact]
+        public void Ranges_Check_For_Intersections()
+        {
+            var a = new Range(1, 1, 1, 3);
+            var b = new Range(2, 1, 2, 3);
+            var c = new Range(1, 3, 2, 1);
+
+            a.Intersects(b).Should().BeFalse();
+            a.IntersectsOrTouches(b).Should().BeFalse();
+
+            a.Intersects(c).Should().BeFalse();
+            a.IntersectsOrTouches(c).Should().BeTrue();
+
+            b.Intersects(c).Should().BeFalse();
+            b.IntersectsOrTouches(c).Should().BeTrue();
+
+        }
+
+        [Fact]
+        public void Ranges_Can_Be_Sorted_By_Start()
+        {
+            var a = new Range(0, 0, 0, 1);
+            var b = new Range(0, 0, 0, 5);
+            var c = new Range(1, 0, 1, 1);
+            var d = new Range(2, 0, 2, 1);
+            var e = new Range(3, 0, 3, 1);
+
+            var list = new[] { b, a, d, e, c };
+
+            list.OrderBy(z => z, Range.CompareUsingStarts)
+                .Should()
+                .ContainInOrder(a, b, c, d);
+        }
+
+        [Fact]
+        public void Ranges_Can_Be_Sorted_By_End()
+        {
+            var a = new Range(0, 0, 0, 1);
+            var b = new Range(0, 0, 0, 5);
+            var c = new Range(1, 0, 1, 1);
+            var d = new Range(2, 0, 2, 1);
+            var e = new Range(3, 0, 3, 1);
+
+            var list = new[] { b, a, d, e, c };
+
+            list.OrderByDescending(z => z, Range.CompareUsingEnds)
+                .Should()
+                .ContainInOrder(e, d, c, b, a);
+        }
     }
 }
diff --git a/test/Lsp.Tests/Testing/TestContentTests.cs b/test/Lsp.Tests/Testing/TestContentTests.cs
new file mode 100644
index 000000000..7c0a9e53e
--- /dev/null
+++ b/test/Lsp.Tests/Testing/TestContentTests.cs
@@ -0,0 +1,229 @@
+using System;
+using FluentAssertions;
+using NSubstitute;
+using OmniSharp.Extensions.LanguageProtocol.Testing;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Lsp.Tests.Testing
+{
+    public class TestContentTests : AutoTestBase
+    {
+        public TestContentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+        {
+        }
+
+        [Fact]
+        public void Should_Parse_Locations_1()
+        {
+            var content = TestContent.Parse(@"012$$3456789");
+            content.Code.Should().Be("0123456789");
+            content.Index.Should().Be(3);
+            content.Lines.Should().HaveCount(1);
+
+            content.GetPositionAtIndex().Should().Be(new Position(0, 3));
+            content.GetIndexAtPosition(new Position(0, 5)).Should().Be(5);
+        }
+
+        [Theory]
+        [InlineData(@"01|]2345", "Saw |] without matching [|")]
+        [InlineData(@"01|}name:2345", "Saw |} without matching {|")]
+        public void Throw_Error_On_Range(string text, string message)
+        {
+            Action a = () => TestContent.Parse(text);
+            a.Should().Throw().WithMessage(message);
+        }
+
+        [Fact]
+        public void Should_Parse_Locations_2()
+        {
+            var content = TestContent.Parse(
+                @"0
+1
+2
+$$3
+4
+5
+6
+7
+8
+9"
+            );
+            content.Code.Should().Be(
+                @"0
+1
+2
+3
+4
+5
+6
+7
+8
+9".NormalizeLineEndings()
+            );
+            content.Index.Should().Be(6);
+            content.Lines.Should().HaveCount(10);
+
+            content.GetPositionAtIndex().Should().Be(new Position(3, 0));
+            content.GetIndexAtPosition(new Position(4, 0)).Should().Be(8);
+        }
+
+        [Theory]
+        [InlineData('{', '}')]
+        [InlineData('[', ']')]
+        [InlineData('(', ')')]
+        public void Position_Marker_Should_be_configurable(char first, char end)
+        {
+            var content = $"hello {first}{end}this is a test";
+            var testContent = TestContent.Parse(content, new TestContentOptions() {
+                PositionMarker = (first, end)
+            });
+            testContent.HasIndex.Should().BeTrue();
+            testContent.Index.Should().Be(6);
+        }
+
+        [Theory]
+        [InlineData('{', '}', '|')]
+        [InlineData('[', ']', '|')]
+        [InlineData('(', ')', '|')]
+        public void Ranges_Should_be_configurable(char start, char end, char term)
+        {
+            var content = $"hello {start}{term}this is a {term}{end}test";
+            var testContent = TestContent.Parse(content, new TestContentOptions() {
+                RangeMarker = ((start, term), (term, end))
+            });
+            testContent.GetRanges().Should().HaveCount(1);
+            testContent.ExtractRange(testContent.GetRanges()[0]).Should().Be("this is a ");
+        }
+
+        [Theory]
+        [InlineData('{', '}', '|', '?')]
+        [InlineData('-', '-', '|', ':')]
+        [InlineData('(', ')', '|', '-')]
+        public void Named_Ranges_Should_be_configurable(char start, char end, char term, char nameEnd)
+        {
+            var content = $"hello {start}{term}test{nameEnd}this is a {term}{end}test";
+            var testContent = TestContent.Parse(content, new TestContentOptions() {
+                NamedRangeMarker = ((start, term), nameEnd, (term, end))
+            });
+            testContent.GetRanges("test").Should().HaveCount(1);
+            testContent.ExtractRange(testContent.GetRanges("test")[0]).Should().Be("this is a ");
+        }
+
+        [Fact]
+        public void Should_Parse_Spans_1()
+        {
+            var content = TestContent.Parse(
+                @"
+[|if (true) {
+  [|var a = 1;|]
+  [|var b = 2;|]
+  [|var c = 3;|]
+[|var other = new {
+  [|value = true|]
+};|]
+|]"
+            );
+            content.Code.Should().Be(
+                @"
+if (true) {
+  var a = 1;
+  var b = 2;
+  var c = 3;
+var other = new {
+  value = true
+};
+".NormalizeLineEndings()
+            );
+            content.Index.Should().Be(-1);
+            content.Lines.Should().HaveCount(9);
+
+
+            var ranges = content.GetRanges();
+            ranges.Should().HaveCount(6);
+            content.ExtractRange(ranges[0]).Should().Be(
+                @"if (true) {
+  var a = 1;
+  var b = 2;
+  var c = 3;
+var other = new {
+  value = true
+};
+".NormalizeLineEndings()
+            );
+            content.ExtractRange(ranges[1]).Should().Be(@"var a = 1;");
+            content.ExtractRange(ranges[2]).Should().Be(@"var b = 2;");
+            content.ExtractRange(ranges[3]).Should().Be(@"var c = 3;");
+            content.ExtractRange(ranges[4]).Should().Be(
+                @"var other = new {
+  value = true
+};".NormalizeLineEndings()
+            );
+            content.ExtractRange(ranges[5]).Should().Be(@"value = true");
+        }
+
+        [Fact]
+        public void Should_Parse_Named_Spans_1()
+        {
+            var content = TestContent.Parse(
+                @"
+{|first:if (true) {
+  {|a:var a = 1;|}
+  {|b:var b = 2;|}
+  {|c:var c = 3;|}
+{|other:var other = new {
+  {|other:value = true|}
+};|}
+|}"
+            );
+            content.Code.Should().Be(
+                @"
+if (true) {
+  var a = 1;
+  var b = 2;
+  var c = 3;
+var other = new {
+  value = true
+};
+".NormalizeLineEndings()
+            );
+            content.Index.Should().Be(-1);
+            content.Lines.Should().HaveCount(9);
+
+
+            var ranges = content.GetRanges("first");
+            ranges.Should().HaveCount(1);
+            content.ExtractRange(ranges[0]).Should().Be(
+                @"if (true) {
+  var a = 1;
+  var b = 2;
+  var c = 3;
+var other = new {
+  value = true
+};
+".NormalizeLineEndings()
+            );
+            ranges = content.GetRanges("a");
+            ranges.Should().HaveCount(1);
+            content.ExtractRange(ranges[0]).Should().Be(@"var a = 1;");
+
+            ranges = content.GetRanges("b");
+            ranges.Should().HaveCount(1);
+            content.ExtractRange(ranges[0]).Should().Be(@"var b = 2;");
+
+            ranges = content.GetRanges("c");
+            ranges.Should().HaveCount(1);
+            content.ExtractRange(ranges[0]).Should().Be(@"var c = 3;");
+
+            ranges = content.GetRanges("other");
+            ranges.Should().HaveCount(2);
+            content.ExtractRange(ranges[0]).Should().Be(
+                @"var other = new {
+  value = true
+};".NormalizeLineEndings()
+            );
+            content.ExtractRange(ranges[1]).Should().Be(@"value = true");
+        }
+    }
+}