IDE/LSP์์ ๐ก(assists) ๊ธฐ๋ฅ์ ๊ตฌํํ ๋, ์ํ ๋ณํ๋ก ์ธํด ๋ฐ์ํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ์ธํฐํ์ด์ค/ํจํด์ ์ค๋ช ํ๊ณ rust-analyzer๊ฐ ์ฌ์ฉํ๋ ์ ๊ทผ๋ฒ์ ์๊ฐํ๋ค.
URL: https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html
Title: How to Make a ๐ก?
IDE/LSP ์๋ฒ๋ (๋์ ์กฐ๊ธ ์ฐํธ๋ ค ๋ณด๋ฉด) ์น ์๋ฒ์ ์ฝ๊ฐ ๋น์ทํ๊ฒ ๋์ํฉ๋๋ค. โ23๋ฒ์งธ ์ค์ ์ฌ๋ณผ ์ ์๊ฐ ๋ญ์ผ?โ ๊ฐ์ ์์ฒญ์ ๋ฐ์์, ์ธ์ด ์๋ฏธ๋ก ์ ๋ฐ๋ผ ์ฒ๋ฆฌํ ๋ค์ ์๋ต์ ๋๋ ค์ค๋๋ค. ์ด๋ค ์์ฒญ์ ๋ฐ์ดํฐ ๋ชจ๋ธ ์์ฒด๋ฅผ ์์ ํ๊ธฐ๋ ํฉ๋๋ค(โfoo.rs ํ์ผ์ ์ ํ ์คํธ๋ 'โฆโ'์ผโ). ์ผ๋ฐ์ ์ผ๋ก, ์ด๋ค ๋ ์์ฒญ ์ฌ์ด์์๋ ์ธ๊ณ์ ์ํ๋ ๋ฐ๋ ์ ์์ต๋๋ค.
์ด ์ ์ ๐ก ๊ธฐ๋ฅ๊ณผ ๊ด๋ จ์ด ์๋๋ฐ, ๋ณดํต ์ด ๊ธฐ๋ฅ์ ๋ ๋ฒ์ ์์ฒญ์ด ํ์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ฒซ ๋ฒ์งธ ์์ฒญ์ ํ์ฌ ์ปค์ ์์น๋ฅผ ๋ฐ์์ ์ฌ์ฉ ๊ฐ๋ฅํ assist ๋ชฉ๋ก์ ๋ฐํํฉ๋๋ค. ๋ชฉ๋ก์ด ๋น์ด ์์ง ์๋ค๋ฉด ํธ์ง๊ธฐ์ ๐ก ์์ด์ฝ์ด ํ์๋ฉ๋๋ค.
๋ ๋ฒ์งธ ์์ฒญ์ ์ฌ์ฉ์๊ฐ ํน์ assist๋ฅผ ํด๋ฆญํ ๋(๋๋ ํด๋ฆญํ๋ฉด) ์ด๋ฃจ์ด์ง๋๋ค. ์ด ์์ฒญ์ ํด๋น diff๋ฅผ ๊ณ์ฐํฉ๋๋ค.
๋ ์์ฒญ ๋ชจ๋ ์ฌ์ฉ์์ ํ๋์ผ๋ก ์์๋๋ฉฐ, ๊ทธ ์ฌ์ด์ ์์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ assist๋ list์ apply ๋์ ์ฌ์ด์ ์ธ๊ณ์ ์ํ๊ฐ ๊ทธ๋๋ก ์ ์ง๋๋ค๊ณ ๊ฐ์ ํ ์ ์์ต๋๋ค.
์ด๋ก๋ถํฐ assist๋ฅผ ์ํ ๋ค์๊ณผ ๊ฐ์ ์ธํฐํ์ด์ค๊ฐ ๋์ต๋๋ค(์ธํ
๋ฆฌJ์ IntentionAction๋ฅผ ์ฝ๊ฐ ์์ ํ ๊ฒ):
1
2
3
4
5
interface IntentionAction {
val name: String
fun isAvailable(position: CursorPosition): Boolean
fun invoke(position: CursorPosition): Diff
}
์ฆ, ์ assist๋ฅผ ๊ตฌํํ๋ ค๋ฉด IntentionAction ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ํด๋์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ทธ๋ฌ๋ฉด IDE ํ๋ซํผ์ด isAvailable๊ณผ getName์ ์ฌ์ฉํด ๐ก ๋ฉ๋ด๋ฅผ ์ฑ์ฐ๊ณ , ์ฌ์ฉ์๊ฐ ์ํ ๋ invoke๋ฅผ ํธ์ถํด assist๋ฅผ ์ ์ฉํฉ๋๋ค.
์ด ์ธํฐํ์ด์ค๋ IDE ํ๋ซํผ ์ ์ฅ์์๋ ์ ํํ ์๋ง์ ํํ์ง๋ง, ๊ตฌํํ๊ธฐ์๋ ์ด์ํฉ๋๋ค.
๋๋ถ๋ถ์ ๊ฒฝ์ฐ isAvailable๊ณผ invoke์ ์์ ๋ถ๋ถ ์ฝ๋๋ ๋น์ทํ ๊ฒ์
๋๋ค. PyCharm์ ๋ ํฐ ์์๋ isAvailable์ invoke๋ฅผ ๋ณด์ธ์.
1
2
3
4
5
6
7
8
9
10
class RsIntentionAction<Ctx>: IntentionAction {
fun getContext(position: CursorPosition): Ctx?
fun invoke(position: CursorPosition, ctx: Ctx): Diff
override fun isAvailable(position: CursorPosition) =
getContext(position) != null
override fun invoke(position: CursorPosition) =
invoke(position, getContext(position)!!)
}
์ค๋ณต์ ๋ค์ ๋ฌด์ํ ๋ฐฉ์์ผ๋ก ์ ๊ฑฐ๋ฉ๋๋ค. isAvailable๊ณผ invoke ์ฌ์ด์ ๊ณตํต ์ฝ๋๋ฅผ (assist๋ณ) Ctx ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ก ๊ตฌ์ฒดํ(reify)ํ๋ ๊ฒ์
๋๋ค. ์ด ๋ฐฉ์์ ๋ชฉ์ ์ ๋ฌ์ฑํ๊ธด ํ์ง๋ง, (๊ทธ์ ์ด๊ฒ์ ๊ฒ ๋ด๋ ์ฃผ๋จธ๋์ ๋ถ๊ณผํ) Context ํ์
์ ์ ์ํ๋ ์ผ์ด ๋ฒ๊ฑฐ๋กญ์ต๋๋ค. ์๋ฅผ ๋ค์ด InvertIfIntention.kt์์ ์ด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
rust-analyzer๋ ์ ๊ฐ ๋๋ผ๊ธฐ์ ์ฝ๊ฐ ๋ ๋์ ํจํด์ ์ฌ์ฉํฉ๋๋ค. IDE์ ์น ์๋ฒ๋ฅผ ๋น๊ตํ๋ ์๋์ ๋น์ ๋ฅผ ๋ ์ฌ๋ ค ๋ด
์๋ค. ์ด๋ฅผ ๋ ํ์ฅํด ๋ณด๋ฉด, assist๋ HTML ํผ๊ณผ ๋น์ทํ๋ค๊ณ ๋งํ ์๋ ์์ต๋๋ค. list ๋์์ ํผ์ ๋ค๋ฃฐ ๋์ GET์ ๋์๋๊ณ , apply๋ POST์ฒ๋ผ ๋ณด์
๋๋ค. HTTP ์๋ฒ์์๋ GET /my-form๊ณผ POST /my-form ์ฌ์ด์ ์ธ๊ณ์ ์ํ๊ฐ ๋ฐ๋๊ธฐ ๋๋ฌธ์, HTTP ์๋ฒ ์ญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ ๋ฒ ์กฐํํฉ๋๋ค.
Django ์น ํ๋ ์์ํฌ์๋ ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํ ๋ฉ์ง ํจํด์ด ์๋๋ฐ, ํจ์ ๊ธฐ๋ฐ ๋ทฐ(function based views)์ ๋๋ค.
1
2
3
4
5
6
7
def my_form(request):
ctx = fetch_stuff_from_postgres()
if request.method == 'POST':
# apply changes ...
else:
# render template ...
ํ๋์ ํจ์๊ฐ GET๊ณผ POST๋ฅผ ๋ชจ๋ ์ฒ๋ฆฌํฉ๋๋ค. ๊ณตํต ๋ถ๋ถ์ ํ ๋ฒ๋ง ์ฒ๋ฆฌํ๊ณ , ์ฐจ์ด์ ์ if์ ๋ ๋ถ๊ธฐ์์ ์ฒ๋ฆฌํ๋ฉฐ, ๋ฐํ์ ๋งค๊ฐ๋ณ์๊ฐ if์ ์ด๋ ๋ถ๊ธฐ๋ฅผ ์ ํํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
์ด ํจํด์ ํ์ด์ฌ ์น ํ๋ ์์ํฌ์์ ๋ฌ์คํธ IDE๋ก ์ฎ๊ธฐ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum MaybeDiff {
Delayed,
Diff(Diff),
}
fn assist(position: CursorPosition, delay: bool)
-> Option<MaybeDiff>
{
let ctx = compute_common_context(position)?;
if delay {
return Some(MaybeDiff::Delayed);
}
let diff = compute_diff(position, ctx);
Some(MaybeDiff::Diff(diff))
}
Context ํ์
์ ๋ก์ปฌ ๋ณ์๋ค์ ์งํฉ์ผ๋ก ๋
น์๋ค์ด ์ฌ๋ผ์ก์ต๋๋ค. ๋๋ ๋์น๋ก ๋งํ๋ฉด, Context๋ ์ ์ด ํ๋ฆ(control flow)์ ๊ตฌ์ฒดํ์
๋๋ค. ์ฆ, if ์ด์ ์ ์ด์ ์๋(live) ๋ก์ปฌ ๋ณ์๋ค์ ์งํฉ์
๋๋ค. ์ด ํจํด์ ์ฝ๋ฃจํด/์ ๋๋ ์ดํฐ/async๋ก ๊ตฌํํ๊ณ ์ถ์ ์๋ ์์ง๋ง, ์ค์ ๋ก๋ ๊ณ ์ ๋ ์ค๋จ(suspension) ์ง์ ์ด ํ๋๋ฟ์ด๋ฏ๋ก ๊ทธ๋ด ํ์๋ ์์ต๋๋ค.
๋จ์ํ๋์ง ์์ ์์๋ invert_if.rs๋ฅผ ์ฐธ๊ณ ํ์ธ์.