私の所属チームでCursorの導入が決まり、その機能と基本的な使い方についてざっくり調べたのでまとめておきます!
実際に触ってみたレポも記載しています!
※インストール方法やCoursorの契約関連に関しては、本記事の対象外とさせていただきます。

Cursorとは

Cursorは、GoogleのGeminiなどの強力な大規模言語モデル(LLM)と連携し、開発者のコーディング作業を強力に支援するために設計されたAIネイティブのコードエディタです。VS Codeをベースに開発されているため、VS Codeユーザーであれば馴染みやすいかと思います。

単なるコードエディタではなく、AIが「コーディングパートナー」として、コードの生成から既存コードの理解、デバッグ、リファクタリングまで、開発プロセスのあらゆる段階で強力なサポートを提供してくれます。

画面構成

Cursorを立ち上げるとこのような画面が表示されます。
「Open Project」から対象のファイルを開きます。

プロジェクトを開いた状態での画面がこちらです。

①主要機能へのアクセス(左サイドバー)

  • Explorer:プロジェクトのファイルやフォルダを表示する領域。
  • Search:ファイル内のワード検索や一括置換など。
  • Source Control:Gitソース管理の領域。現在のリポジトリの変更の詳細(変更、ステージングされた変更、マージされた変更)が表示されます。
  • Extentions:拡張機能のマーケットプレイス。検索してインストールすることができます。

プルダウンアイコンを開くとその他の機能が表示されます。よく使う機能はピン留めすると便利です。

② エディタ(中央)
実際にコードを記述・編集する領域。AIが生成したコードや、手動で修正したコードが表示されます。

③ AIチャット / コマンドパレット(右サイドバー)
CursorのAIと対話する主要なインターフェース。
ここに自然言語(日本語でOK!)で指示(プロンプト)を入力することで、AIがコードを生成したり、既存コードの説明や修正提案をしてくれたりします。
Cursorの最も特徴的な機能の一つが、このAIチャットインターフェースです。

VS Codeと基本的な動きやコマンドは一緒です。VS Codeにインストール済みの拡張機能や設定も、Coursorに引き継ぐことができます。

機能について

Chat

テキストフィールドに自然言語で入力して、AIに対話できる機能です。
以下画像内の「+」で新規チャットページの作成ができたり、時計のようなアイコンでAIとの対話履歴を確認することができます。

Command + K

AIによるコード修正や生成をするためのショートカットの機能です。
既存のコードをハイライトした上で、Command + Kを実行して、AIによるコード修正が可能です。

画像引用元:Cursor公式サイト

Use Documentation

AIに特定のドキュメントの内容を学習させ、その情報に基づいて生成や回答をさせる機能です。
サードパーティ製のライブラリやフレームワークを使用する際に、AIが最新のデータやバージョンに対応した回答を提供できるようにする目的で利用されます。

画像引用元:Cursor公式サイト

Reference your Code

@と入力するだけで、プロジェクト内のフォルダやファイルなどをメンションして参照することができます。複数選択も可能です。

画像引用元:Cursor公式サイト

Use Images

チャットの下の画像ボタン押下、または画像を入力ボックスにドラッグして、チャットに視覚的なコンテキストを含めることができる機能です。
UI画像を添付して、UIクラスを簡単に生成することができます。

画像引用元:Cursor公式サイト

Ask the Web

@WebでCursor が Web を検索し、最新の情報を基に質問に答えてくれる機能です。
回答で参照したサイトも提示してくれるので、サイトへリンクすることも可能です。

画像引用元:Cursor公式サイト

実際に触ってみた

今回は、Flutter言語を使ったスマホアプリを作成してみます。
スマホアプリで最もよく使われるUIの一つ、「ログイン画面」の作成を通じて、CursorのAI機能がどれほどのレベルなのか、実際に検証してみました。
まず、新規Flutterプロジェクトを立ち上げると、デフォルトで簡単なカウントアップアプリの画面が表示されます。この状態から、AIに「ログイン画面」の作成を依頼してみます。

AIチャット(右サイドバーの③のエリア)に、以下のシンプルなプロンプト(指示文)を入力します。

メールアドレス、パスワードを入れてログインができるログイン画面を作成してください

回答がこちらです↓

生成されたコードはこちらです

main.dart

import 'package:flutter/material.dart';

import 'login_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ログインアプリ',
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF667eea)), useMaterial3: true),
      home: const LoginScreen(),
    );
  }
}

login_screen.dart

import 'package:flutter/material.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isPasswordVisible = false;
  bool _isLoading = false;

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  String? _validateEmail(String? value) {
    if (value == null || value.isEmpty) {
      return 'メールアドレスを入力してください';
    }
    if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
      return '有効なメールアドレスを入力してください';
    }
    return null;
  }

  String? _validatePassword(String? value) {
    if (value == null || value.isEmpty) {
      return 'パスワードを入力してください';
    }
    if (value.length < 6) {
      return 'パスワードは6文字以上で入力してください';
    }
    return null;
  }

  Future<void> _handleLogin() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    setState(() {
      _isLoading = true;
    });

    // ログイン処理をシミュレート
    await Future.delayed(const Duration(seconds: 2));

    setState(() {
      _isLoading = false;
    });

    // ログイン成功時の処理
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('ログインに成功しました!'), backgroundColor: Colors.green));

      // ホーム画面に遷移(ここでは簡単な例として)
      Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => const HomeScreen()));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF667eea), Color(0xFF764ba2)]),
        ),
        child: SafeArea(
          child: Center(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(24.0),
              child: Card(
                elevation: 8,
                shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
                child: Padding(
                  padding: const EdgeInsets.all(32.0),
                  child: Form(
                    key: _formKey,
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        // ロゴ・タイトル
                        const Icon(Icons.lock_outline, size: 80, color: Color(0xFF667eea)),
                        const SizedBox(height: 24),
                        const Text('ログイン', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.black87)),
                        const SizedBox(height: 8),
                        const Text('アカウントにログインしてください', style: TextStyle(fontSize: 16, color: Colors.grey)),
                        const SizedBox(height: 32),

                        // メールアドレス入力フィールド
                        TextFormField(
                          controller: _emailController,
                          keyboardType: TextInputType.emailAddress,
                          validator: _validateEmail,
                          decoration: InputDecoration(
                            labelText: 'メールアドレス',
                            hintText: 'example@email.com',
                            prefixIcon: const Icon(Icons.email_outlined),
                            border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
                            focusedBorder: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(12),
                              borderSide: const BorderSide(color: Color(0xFF667eea), width: 2),
                            ),
                          ),
                        ),
                        const SizedBox(height: 20),

                        // パスワード入力フィールド
                        TextFormField(
                          controller: _passwordController,
                          obscureText: !_isPasswordVisible,
                          validator: _validatePassword,
                          decoration: InputDecoration(
                            labelText: 'パスワード',
                            hintText: 'パスワードを入力',
                            prefixIcon: const Icon(Icons.lock_outlined),
                            suffixIcon: IconButton(
                              icon: Icon(_isPasswordVisible ? Icons.visibility : Icons.visibility_off),
                              onPressed: () {
                                setState(() {
                                  _isPasswordVisible = !_isPasswordVisible;
                                });
                              },
                            ),
                            border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
                            focusedBorder: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(12),
                              borderSide: const BorderSide(color: Color(0xFF667eea), width: 2),
                            ),
                          ),
                        ),
                        const SizedBox(height: 24),

                        // ログインボタン
                        SizedBox(
                          width: double.infinity,
                          height: 50,
                          child: ElevatedButton(
                            onPressed: _isLoading ? null : _handleLogin,
                            style: ElevatedButton.styleFrom(
                              backgroundColor: const Color(0xFF667eea),
                              foregroundColor: Colors.white,
                              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
                              elevation: 2,
                            ),
                            child:
                                _isLoading
                                    ? const SizedBox(
                                      height: 20,
                                      width: 20,
                                      child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation<Color>(Colors.white)),
                                    )
                                    : const Text('ログイン', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                          ),
                        ),
                        const SizedBox(height: 16),

                        // パスワードを忘れた場合のリンク
                        TextButton(
                          onPressed: () {
                            ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('パスワードリセット機能は実装予定です')));
                          },
                          child: const Text('パスワードを忘れた場合', style: TextStyle(color: Color(0xFF667eea))),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// ホーム画面(ログイン後の画面)
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ホーム'),
        backgroundColor: const Color(0xFF667eea),
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () {
              Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => const LoginScreen()));
            },
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.check_circle, size: 100, color: Colors.green),
            const SizedBox(height: 24),
            const Text('ログイン成功!', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
            const SizedBox(height: 16),
            const Text('ようこそ!', style: TextStyle(fontSize: 18, color: Colors.grey)),
            const SizedBox(height: 48),
          ],
        ),
      ),
    );
  }
}

カウントアップアプリのUIと起動部分が実装されていたmain.dartが上書きされ、
ログイン画面とその処理をまとめたlogin_screen.dartが追加されました。
出来上がったログイン画面がこちらです。↓

実際に動かしてみましょう!
(再生速度は倍速になっています。)

完成度が高くてびっくりっっ!!!!
いわゆる一般的な馴染みのあるデザインで作成されていて直感的に操作できます。
指示していなかったバリデーション機能やログイン処理、パスワードの非表示、ログイン後の画面までも実装してくれました、、、。

プロンプトはたった1行のみです。

感想

自然言語でざっくりしたイメージを伝えるだけでコードが生成される、まさに「これがバイブコーディングか」と実感しました。正直、こんなにできてしまうのか、という驚きは隠せません。。。
一般的で、よくあるパターン、に関しての生成はかなり精度が高く、期待以上の結果でした。

こうしたツールを触ってみると、技術力そのものも大事ですが、それ以上に「このサービスで何を解決したいのか」という本質的な部分こそ、人間が考える価値のある領域だと感じます。
単に言われた通りに作るや、技術の凝ったものを作るではなく、目的に沿った開発をすることが大事なんだ、と改めて思いました。
今回使ってみて非常に優秀だとわかったので、今後は良きパートナーとしてAIをうまく活用していけたらと思います🤝